Skip to content

Commit 77229f4

Browse files
committed
Snapshot tests
1 parent 29bb31f commit 77229f4

12 files changed

Lines changed: 299 additions & 30 deletions

File tree

apps/dashboard/src/lib/tokens.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export async function decodeAccessToken(accessToken: string) {
4141
if (error instanceof JWTExpired) {
4242
throw new KnownErrors.AccessTokenExpired();
4343
} else if (error instanceof JOSEError) {
44+
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);
4445
throw new KnownErrors.UnparsableAccessToken();
4546
}
4647
throw error;

apps/e2e/.env

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
SERVER_BASE_URL=
1+
DASHBOARD_BASE_URL=
2+
BACKEND_BASE_URL=
23
INTERNAL_PROJECT_ID=
34
INTERNAL_PROJECT_CLIENT_KEY=
45
INTERNAL_PROJECT_SERVER_KEY=

apps/e2e/.env.development

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
SERVER_BASE_URL=http://localhost:8101
1+
DASHBOARD_BASE_URL=http://localhost:8101
2+
BACKEND_BASE_URL=http://localhost:8102
23
INTERNAL_PROJECT_ID=internal
34
INTERNAL_PROJECT_CLIENT_KEY=this-publishable-client-key-is-for-local-development-only
45
INTERNAL_PROJECT_SERVER_KEY=this-secret-server-key-is-for-local-development-only

apps/e2e/package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@
99
"lint": "eslint --ext .tsx,.ts .",
1010
"clean": "rimraf dist && rimraf node_modules"
1111
},
12-
"dependencies": {},
13-
"devDependencies": {
14-
"dotenv": "^16.4.5"
15-
}
12+
"dependencies": {
13+
"dotenv": "^16.4.5",
14+
"@stackframe/stack-shared": "workspace:*"
15+
},
16+
"devDependencies": {}
1617
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { BACKEND_BASE_URL, NiceResponse, niceFetch } from "../helpers";
2+
3+
export function niceBackendFetch(url: string, options?: RequestInit): Promise<NiceResponse> {
4+
const res = niceFetch(new URL(url, BACKEND_BASE_URL), options);
5+
return res;
6+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { describe, expect, test } from "vitest";
2+
import { niceBackendFetch } from "../../../backend-helpers";
3+
4+
describe("Backend index page", () => {
5+
test("Main Page", async () => {
6+
const response = await niceBackendFetch("/api/v1");
7+
expect(response).toMatchInlineSnapshot(`
8+
NiceResponse {
9+
"status": 200,
10+
"headers": _Headers {
11+
"x-stack-request-id": <stripped header 'x-stack-request-id'>,
12+
<several headers hidden>,
13+
},
14+
"body": "Welcome to the Stack API endpoint! Please refer to the documentation at https://docs.stack-auth.com.\\n\\nAuthentication: None",
15+
}
16+
`);
17+
});
18+
});

apps/e2e/tests/api/internal-project.test.ts renamed to apps/e2e/tests/dashboard/internal-project.test.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { describe, expect, test } from "vitest";
22
import request from "supertest";
3-
import { BASE_URL, INTERNAL_PROJECT_CLIENT_KEY, INTERNAL_PROJECT_ID } from "../helpers";
3+
import { DASHBOARD_BASE_URL, INTERNAL_PROJECT_CLIENT_KEY, INTERNAL_PROJECT_ID } from "../helpers";
44
import crypto from "crypto";
55

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

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

4040
describe("Various internal project tests", () => {
4141
test("Main Page", async () => {
42-
const response = await request(BASE_URL).get("/");
42+
const response = await request(DASHBOARD_BASE_URL).get("/");
4343
expect(response.status).toBe(307);
4444
});
4545

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

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

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

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

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

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

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

148-
const response2 = await request(BASE_URL).get("/api/v1").set({
148+
const response2 = await request(DASHBOARD_BASE_URL).get("/api/v1").set({
149149
...AUTH_HEADER,
150150
'x-stack-request-type': 'client',
151151
'authorization': 'StackSession ' + response.body.accessToken,

apps/e2e/tests/helpers.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { Nicifiable } from "@stackframe/stack-shared/dist/utils/strings";
2+
13
function getEnvVar(name: string): string {
24
const value = process.env[name];
35
if (!value) {
@@ -6,6 +8,34 @@ function getEnvVar(name: string): string {
68
return value;
79
}
810

9-
export const BASE_URL = getEnvVar("SERVER_BASE_URL");
11+
12+
export class NiceResponse implements Nicifiable {
13+
constructor(
14+
public readonly status: number,
15+
public readonly headers: Headers,
16+
public readonly body: unknown,
17+
) {}
18+
19+
getNicifiableKeys(): string[] {
20+
// reorder the keys for nicer printing
21+
return ["status", "headers", "body"];
22+
}
23+
};
24+
25+
export async function niceFetch(url: string | URL, options?: RequestInit): Promise<NiceResponse> {
26+
const fetchRes = await fetch(url, options);
27+
let body;
28+
if (fetchRes.headers.get("content-type")?.includes("application/json")) {
29+
body = await fetchRes.json();
30+
} else if (fetchRes.headers.get("content-type")?.includes("text")) {
31+
body = await fetchRes.text();
32+
} else {
33+
body = await fetchRes.arrayBuffer();
34+
}
35+
return new NiceResponse(fetchRes.status, fetchRes.headers, body);
36+
}
37+
38+
export const DASHBOARD_BASE_URL = getEnvVar("DASHBOARD_BASE_URL");
39+
export const BACKEND_BASE_URL = getEnvVar("BACKEND_BASE_URL");
1040
export const INTERNAL_PROJECT_ID = getEnvVar("INTERNAL_PROJECT_ID");
1141
export const INTERNAL_PROJECT_CLIENT_KEY = getEnvVar("INTERNAL_PROJECT_CLIENT_KEY");
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { SnapshotSerializer } from "vitest";
2+
import { Nicifiable, nicify } from "@stackframe/stack-shared/dist/utils/strings";
3+
import { typedIncludes } from "@stackframe/stack-shared/dist/utils/arrays";
4+
5+
const stackSnapshotSerializerSymbol = Symbol("stackSnapshotSerializer");
6+
7+
const hideHeaders = [
8+
"access-control-allow-headers",
9+
"access-control-allow-methods",
10+
"access-control-allow-origin",
11+
"access-control-expose-headers",
12+
"cache-control",
13+
"connection",
14+
"content-security-policy",
15+
"content-type",
16+
"cross-origin-opener-policy",
17+
"date",
18+
"keep-alive",
19+
"permissions-policy",
20+
"referrer-policy",
21+
"transfer-encoding",
22+
"vary",
23+
"x-content-type-options",
24+
"x-frame-options",
25+
];
26+
27+
const stripHeaders = ["x-stack-request-id"];
28+
29+
const snapshotSerializer: SnapshotSerializer = {
30+
serialize(val, config, indentation, depth, refs, printer) {
31+
return nicify(val, {
32+
currentIndent: indentation,
33+
maxDepth: config.maxDepth - depth,
34+
refs: new Map(refs.map((ref, i) => [ref, `vitestRef[${i}]`])),
35+
lineIndent: config.indent,
36+
multiline: true,
37+
path: "snapshot",
38+
overrides: (value, options) => {
39+
const stackSnapshotSerializer: null | {
40+
headersHidden?: true,
41+
} = (value as any)[stackSnapshotSerializerSymbol];
42+
43+
// Hide headers
44+
if (value instanceof Headers && !stackSnapshotSerializer?.headersHidden) {
45+
const originalHeaders = [...value.entries()];
46+
const filteredHeaders = originalHeaders.filter(([key]) => !typedIncludes(hideHeaders, key.toLowerCase()));
47+
return ["replace", Object.assign(new Headers(filteredHeaders), {
48+
[stackSnapshotSerializerSymbol]: {
49+
headersHidden: true,
50+
},
51+
getNicifiedObjectExtraLines: () => [`<several headers hidden>`],
52+
})];
53+
}
54+
55+
// Strip headers
56+
if (options?.parent?.value instanceof Headers) {
57+
const headerName = options.keyInParent?.toString().toLowerCase();
58+
if (typedIncludes(stripHeaders, headerName)) {
59+
return ["result", `<stripped header '${headerName}'>`];
60+
}
61+
}
62+
63+
// Otherwise, use default serialization
64+
return null;
65+
},
66+
});
67+
},
68+
test(val) {
69+
return true;
70+
},
71+
};
72+
export default snapshotSerializer;

apps/e2e/vitest.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@ export default defineConfig({
77
environment: 'node',
88
testTimeout: 20_000,
99
globalSetup: './tests/global-setup.ts',
10+
snapshotSerializers: ["./tests/snapshot-serializer.ts"],
1011
},
1112
})

0 commit comments

Comments
 (0)