Skip to content

Commit

Permalink
Snapshot tests
Browse files Browse the repository at this point in the history
  • Loading branch information
N2D4 committed Jun 19, 2024
1 parent 29bb31f commit 77229f4
Show file tree
Hide file tree
Showing 12 changed files with 299 additions and 30 deletions.
1 change: 1 addition & 0 deletions apps/dashboard/src/lib/tokens.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export async function decodeAccessToken(accessToken: string) {
if (error instanceof JWTExpired) {
throw new KnownErrors.AccessTokenExpired();
} else if (error instanceof JOSEError) {
console.log('Unparsable access token found. This may be expected behaviour, for example if they switched Stack hosts, but the information below could be useful for debugging.', { accessToken }, error);
throw new KnownErrors.UnparsableAccessToken();
}
throw error;
Expand Down
3 changes: 2 additions & 1 deletion apps/e2e/.env
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
SERVER_BASE_URL=
DASHBOARD_BASE_URL=
BACKEND_BASE_URL=
INTERNAL_PROJECT_ID=
INTERNAL_PROJECT_CLIENT_KEY=
INTERNAL_PROJECT_SERVER_KEY=
3 changes: 2 additions & 1 deletion apps/e2e/.env.development
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
SERVER_BASE_URL=http://localhost:8101
DASHBOARD_BASE_URL=http://localhost:8101
BACKEND_BASE_URL=http://localhost:8102
INTERNAL_PROJECT_ID=internal
INTERNAL_PROJECT_CLIENT_KEY=this-publishable-client-key-is-for-local-development-only
INTERNAL_PROJECT_SERVER_KEY=this-secret-server-key-is-for-local-development-only
9 changes: 5 additions & 4 deletions apps/e2e/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
"lint": "eslint --ext .tsx,.ts .",
"clean": "rimraf dist && rimraf node_modules"
},
"dependencies": {},
"devDependencies": {
"dotenv": "^16.4.5"
}
"dependencies": {
"dotenv": "^16.4.5",
"@stackframe/stack-shared": "workspace:*"
},
"devDependencies": {}
}
6 changes: 6 additions & 0 deletions apps/e2e/tests/backend/backend-helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { BACKEND_BASE_URL, NiceResponse, niceFetch } from "../helpers";

export function niceBackendFetch(url: string, options?: RequestInit): Promise<NiceResponse> {
const res = niceFetch(new URL(url, BACKEND_BASE_URL), options);
return res;
}
18 changes: 18 additions & 0 deletions apps/e2e/tests/backend/endpoints/api/v1/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { describe, expect, test } from "vitest";
import { niceBackendFetch } from "../../../backend-helpers";

describe("Backend index page", () => {
test("Main Page", async () => {
const response = await niceBackendFetch("/api/v1");
expect(response).toMatchInlineSnapshot(`

Check failure on line 7 in apps/e2e/tests/backend/endpoints/api/v1/index.test.ts

View workflow job for this annotation

GitHub Actions / build (20.x)

tests/backend/endpoints/api/v1/index.test.ts > Backend index page > Main Page

Error: Snapshot `Backend index page > Main Page 1` mismatched - Expected + Received NiceResponse { "status": 200, - "headers": _Headers { + "headers": Headers { "x-stack-request-id": <stripped header 'x-stack-request-id'>, <several headers hidden>, }, "body": "Welcome to the Stack API endpoint! Please refer to the documentation at https://docs.stack-auth.com.\n\nAuthentication: None", } ❯ tests/backend/endpoints/api/v1/index.test.ts:7:22
NiceResponse {
"status": 200,
"headers": _Headers {
"x-stack-request-id": <stripped header 'x-stack-request-id'>,
<several headers hidden>,
},
"body": "Welcome to the Stack API endpoint! Please refer to the documentation at https://docs.stack-auth.com.\\n\\nAuthentication: None",
}
`);
});
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect, test } from "vitest";
import request from "supertest";
import { BASE_URL, INTERNAL_PROJECT_CLIENT_KEY, INTERNAL_PROJECT_ID } from "../helpers";
import { DASHBOARD_BASE_URL, INTERNAL_PROJECT_CLIENT_KEY, INTERNAL_PROJECT_ID } from "../helpers";
import crypto from "crypto";

const AUTH_HEADER = {
Expand All @@ -19,7 +19,7 @@ function randomString() {
async function signUpWithEmailPassword() {
const email = randomString() + "@stack-test.example.com";
const password = randomString();
const response = await request(BASE_URL).post("/api/v1/auth/signup").set(AUTH_HEADER).set(JSON_HEADER).send({
const response = await request(DASHBOARD_BASE_URL).post("/api/v1/auth/signup").set(AUTH_HEADER).set(JSON_HEADER).send({
email,
password,
emailVerificationRedirectUrl: 'https://localhost:3000/verify-email',
Expand All @@ -29,7 +29,7 @@ async function signUpWithEmailPassword() {
}

async function signInWithEmailPassword(email: string, password: string) {
const response = await request(BASE_URL).post("/api/v1/auth/signin").set(AUTH_HEADER).set(JSON_HEADER).send({
const response = await request(DASHBOARD_BASE_URL).post("/api/v1/auth/signin").set(AUTH_HEADER).set(JSON_HEADER).send({
email,
password,
});
Expand All @@ -39,12 +39,12 @@ async function signInWithEmailPassword(email: string, password: string) {

describe("Various internal project tests", () => {
test("Main Page", async () => {
const response = await request(BASE_URL).get("/");
const response = await request(DASHBOARD_BASE_URL).get("/");
expect(response.status).toBe(307);
});

test("API root (no authentication)", async () => {
const response = await request(BASE_URL).get("/api/v1");
const response = await request(DASHBOARD_BASE_URL).get("/api/v1");
expect(response.status).toBe(200);
expect(response.text).contains("Stack API");
expect(response.text).contains("Authentication: None");
Expand All @@ -63,7 +63,7 @@ describe("Various internal project tests", () => {
});

test("No current user without authentication", async () => {
const response = await request(BASE_URL).get("/api/v1/current-user").set(AUTH_HEADER);
const response = await request(DASHBOARD_BASE_URL).get("/api/v1/current-user").set(AUTH_HEADER);
expect(response.status).toBe(200);
expect(response.body).toBe(null);
});
Expand All @@ -72,7 +72,7 @@ describe("Various internal project tests", () => {
const { email, password, response } = await signUpWithEmailPassword();
await signInWithEmailPassword(email, password);

const response2 = await request(BASE_URL)
const response2 = await request(DASHBOARD_BASE_URL)
.get("/api/v1/current-user")
.set({
...AUTH_HEADER,
Expand All @@ -83,7 +83,7 @@ describe("Various internal project tests", () => {
});

test("Can't get current user with invalid token", async () => {
const response = await request(BASE_URL)
const response = await request(DASHBOARD_BASE_URL)
.get("/api/v1/current-user")
.set({
...AUTH_HEADER,
Expand All @@ -97,7 +97,7 @@ describe("Various internal project tests", () => {
const { email, password, response } = await signUpWithEmailPassword();
await signInWithEmailPassword(email, password);

const response2 = await request(BASE_URL)
const response2 = await request(DASHBOARD_BASE_URL)
.put("/api/v1/current-user")
.set({
...AUTH_HEADER,
Expand All @@ -115,7 +115,7 @@ describe("Various internal project tests", () => {
const { email, password, response } = await signUpWithEmailPassword();
await signInWithEmailPassword(email, password);

const response2 = await request(BASE_URL)
const response2 = await request(DASHBOARD_BASE_URL)
.put("/api/v1/current-user")
.set({
...AUTH_HEADER,
Expand All @@ -130,7 +130,7 @@ describe("Various internal project tests", () => {
});

test("Can't update non-existing user's display name", async () => {
const response = await request(BASE_URL)
const response = await request(DASHBOARD_BASE_URL)
.put("/api/v1/current-user")
.set(AUTH_HEADER)
.set(JSON_HEADER)
Expand All @@ -145,7 +145,7 @@ describe("Various internal project tests", () => {
const { email, password, response } = await signUpWithEmailPassword();
await signInWithEmailPassword(email, password);

const response2 = await request(BASE_URL).get("/api/v1").set({
const response2 = await request(DASHBOARD_BASE_URL).get("/api/v1").set({
...AUTH_HEADER,
'x-stack-request-type': 'client',
'authorization': 'StackSession ' + response.body.accessToken,
Expand Down
32 changes: 31 additions & 1 deletion apps/e2e/tests/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Nicifiable } from "@stackframe/stack-shared/dist/utils/strings";

function getEnvVar(name: string): string {
const value = process.env[name];
if (!value) {
Expand All @@ -6,6 +8,34 @@ function getEnvVar(name: string): string {
return value;
}

export const BASE_URL = getEnvVar("SERVER_BASE_URL");

export class NiceResponse implements Nicifiable {
constructor(
public readonly status: number,
public readonly headers: Headers,
public readonly body: unknown,
) {}

getNicifiableKeys(): string[] {
// reorder the keys for nicer printing
return ["status", "headers", "body"];
}
};

export async function niceFetch(url: string | URL, options?: RequestInit): Promise<NiceResponse> {
const fetchRes = await fetch(url, options);
let body;
if (fetchRes.headers.get("content-type")?.includes("application/json")) {
body = await fetchRes.json();
} else if (fetchRes.headers.get("content-type")?.includes("text")) {
body = await fetchRes.text();
} else {
body = await fetchRes.arrayBuffer();
}
return new NiceResponse(fetchRes.status, fetchRes.headers, body);
}

export const DASHBOARD_BASE_URL = getEnvVar("DASHBOARD_BASE_URL");
export const BACKEND_BASE_URL = getEnvVar("BACKEND_BASE_URL");
export const INTERNAL_PROJECT_ID = getEnvVar("INTERNAL_PROJECT_ID");
export const INTERNAL_PROJECT_CLIENT_KEY = getEnvVar("INTERNAL_PROJECT_CLIENT_KEY");
72 changes: 72 additions & 0 deletions apps/e2e/tests/snapshot-serializer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { SnapshotSerializer } from "vitest";
import { Nicifiable, nicify } from "@stackframe/stack-shared/dist/utils/strings";
import { typedIncludes } from "@stackframe/stack-shared/dist/utils/arrays";

const stackSnapshotSerializerSymbol = Symbol("stackSnapshotSerializer");

const hideHeaders = [
"access-control-allow-headers",
"access-control-allow-methods",
"access-control-allow-origin",
"access-control-expose-headers",
"cache-control",
"connection",
"content-security-policy",
"content-type",
"cross-origin-opener-policy",
"date",
"keep-alive",
"permissions-policy",
"referrer-policy",
"transfer-encoding",
"vary",
"x-content-type-options",
"x-frame-options",
];

const stripHeaders = ["x-stack-request-id"];

const snapshotSerializer: SnapshotSerializer = {
serialize(val, config, indentation, depth, refs, printer) {
return nicify(val, {
currentIndent: indentation,
maxDepth: config.maxDepth - depth,
refs: new Map(refs.map((ref, i) => [ref, `vitestRef[${i}]`])),
lineIndent: config.indent,
multiline: true,
path: "snapshot",
overrides: (value, options) => {
const stackSnapshotSerializer: null | {
headersHidden?: true,
} = (value as any)[stackSnapshotSerializerSymbol];

// Hide headers
if (value instanceof Headers && !stackSnapshotSerializer?.headersHidden) {
const originalHeaders = [...value.entries()];
const filteredHeaders = originalHeaders.filter(([key]) => !typedIncludes(hideHeaders, key.toLowerCase()));
return ["replace", Object.assign(new Headers(filteredHeaders), {
[stackSnapshotSerializerSymbol]: {
headersHidden: true,
},
getNicifiedObjectExtraLines: () => [`<several headers hidden>`],
})];
}

// Strip headers
if (options?.parent?.value instanceof Headers) {
const headerName = options.keyInParent?.toString().toLowerCase();
if (typedIncludes(stripHeaders, headerName)) {
return ["result", `<stripped header '${headerName}'>`];
}
}

// Otherwise, use default serialization
return null;
},
});
},
test(val) {
return true;
},
};
export default snapshotSerializer;
1 change: 1 addition & 0 deletions apps/e2e/vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ export default defineConfig({
environment: 'node',
testTimeout: 20_000,
globalSetup: './tests/global-setup.ts',
snapshotSerializers: ["./tests/snapshot-serializer.ts"],
},
})
Loading

0 comments on commit 77229f4

Please sign in to comment.