@terpjs/conformance 0.5.5 → 0.5.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +43 -0
- package/dist/index.d.ts.map +1 -0
- package/{src/index.ts → dist/index.js} +21 -28
- package/dist/index.js.map +1 -0
- package/package.json +15 -5
- package/playwright.config.ts +0 -19
- package/tests/auth.spec.ts +0 -50
- package/tests/standard.spec.ts +0 -85
- package/tsconfig.json +0 -14
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type Page } from "@playwright/test";
|
|
2
|
+
/**
|
|
3
|
+
* The administrator the base-profile flows sign in as. The default matches the bundled example
|
|
4
|
+
* workbench's seed; a generated repo points these at its own seeded admin via
|
|
5
|
+
* `TERP_E2E_ADMIN_EMAIL` / `TERP_E2E_ADMIN_PASSWORD` (or passes credentials to `login`)
|
|
6
|
+
* without editing the suite.
|
|
7
|
+
*/
|
|
8
|
+
export declare const ADMIN: {
|
|
9
|
+
email: string;
|
|
10
|
+
password: string;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* A writer (rank at/above the write threshold) and a read-only user, for an app's RBAC flows that
|
|
14
|
+
* assert write-gated controls appear for a writer and hide for a viewer. The defaults match the
|
|
15
|
+
* bundled example workbench's seed and are overridable per app via the matching `TERP_E2E_*`
|
|
16
|
+
* variables.
|
|
17
|
+
*/
|
|
18
|
+
export declare const EDITOR: {
|
|
19
|
+
email: string;
|
|
20
|
+
password: string;
|
|
21
|
+
};
|
|
22
|
+
export declare const VIEWER: {
|
|
23
|
+
email: string;
|
|
24
|
+
password: string;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Sign in through the real login screen. App-agnostic: the login screen and session are
|
|
28
|
+
* base-profile (identical in every Terp app), so this is the reusable entry point any app's
|
|
29
|
+
* conformance suite composes. Success is the sign-in screen being replaced by the app shell;
|
|
30
|
+
* callers assert their own landing content afterwards.
|
|
31
|
+
*/
|
|
32
|
+
export declare function login(page: Page, credentials?: {
|
|
33
|
+
email: string;
|
|
34
|
+
password: string;
|
|
35
|
+
}): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Sign out through the shell's account menu and assert the session is gone. Base-profile: the
|
|
38
|
+
* user menu (avatar at the bottom of every Terp app's sidebar) opens Settings and Sign out;
|
|
39
|
+
* sign-out revokes the token server-side (ADR 0031), so this is reusable across apps. Success
|
|
40
|
+
* is the app shell being replaced by the sign-in screen.
|
|
41
|
+
*/
|
|
42
|
+
export declare function logout(page: Page): Promise<void>;
|
|
43
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAErD;;;;;GAKG;AACH,eAAO,MAAM,KAAK;;;CAGjB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,MAAM;;;CAGlB,CAAC;AAEF,eAAO,MAAM,MAAM;;;CAGlB,CAAC;AAEF;;;;;GAKG;AACH,wBAAsB,KAAK,CACzB,IAAI,EAAE,IAAI,EACV,WAAW,GAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAU,GACvD,OAAO,CAAC,IAAI,CAAC,CASf;AAED;;;;;GAKG;AACH,wBAAsB,MAAM,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAItD"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { expect
|
|
2
|
-
|
|
1
|
+
import { expect } from "@playwright/test";
|
|
3
2
|
/**
|
|
4
3
|
* The administrator the base-profile flows sign in as. The default matches the bundled example
|
|
5
4
|
* workbench's seed; a generated repo points these at its own seeded admin via
|
|
@@ -7,10 +6,9 @@ import { expect, type Page } from "@playwright/test";
|
|
|
7
6
|
* without editing the suite.
|
|
8
7
|
*/
|
|
9
8
|
export const ADMIN = {
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
email: process.env.TERP_E2E_ADMIN_EMAIL ?? "admin@acme.test",
|
|
10
|
+
password: process.env.TERP_E2E_ADMIN_PASSWORD ?? "correct horse battery staple",
|
|
12
11
|
};
|
|
13
|
-
|
|
14
12
|
/**
|
|
15
13
|
* A writer (rank at/above the write threshold) and a read-only user, for an app's RBAC flows that
|
|
16
14
|
* assert write-gated controls appear for a writer and hide for a viewer. The defaults match the
|
|
@@ -18,43 +16,38 @@ export const ADMIN = {
|
|
|
18
16
|
* variables.
|
|
19
17
|
*/
|
|
20
18
|
export const EDITOR = {
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
email: process.env.TERP_E2E_EDITOR_EMAIL ?? "editor@acme.test",
|
|
20
|
+
password: process.env.TERP_E2E_EDITOR_PASSWORD ?? "correct horse battery staple",
|
|
23
21
|
};
|
|
24
|
-
|
|
25
22
|
export const VIEWER = {
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
email: process.env.TERP_E2E_VIEWER_EMAIL ?? "viewer@acme.test",
|
|
24
|
+
password: process.env.TERP_E2E_VIEWER_PASSWORD ?? "correct horse battery staple",
|
|
28
25
|
};
|
|
29
|
-
|
|
30
26
|
/**
|
|
31
27
|
* Sign in through the real login screen. App-agnostic: the login screen and session are
|
|
32
28
|
* base-profile (identical in every Terp app), so this is the reusable entry point any app's
|
|
33
29
|
* conformance suite composes. Success is the sign-in screen being replaced by the app shell;
|
|
34
30
|
* callers assert their own landing content afterwards.
|
|
35
31
|
*/
|
|
36
|
-
export async function login(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
await expect(page.getByRole("heading", { name: "Sign in" })).toHaveCount(0, {
|
|
46
|
-
timeout: 15_000,
|
|
47
|
-
});
|
|
32
|
+
export async function login(page, credentials = ADMIN) {
|
|
33
|
+
await page.goto("/");
|
|
34
|
+
await expect(page.getByRole("heading", { name: "Sign in" })).toBeVisible();
|
|
35
|
+
await page.getByPlaceholder("Email").fill(credentials.email);
|
|
36
|
+
await page.getByPlaceholder("Password").fill(credentials.password);
|
|
37
|
+
await page.getByRole("button", { name: "Sign in" }).click();
|
|
38
|
+
await expect(page.getByRole("heading", { name: "Sign in" })).toHaveCount(0, {
|
|
39
|
+
timeout: 15_000,
|
|
40
|
+
});
|
|
48
41
|
}
|
|
49
|
-
|
|
50
42
|
/**
|
|
51
43
|
* Sign out through the shell's account menu and assert the session is gone. Base-profile: the
|
|
52
44
|
* user menu (avatar at the bottom of every Terp app's sidebar) opens Settings and Sign out;
|
|
53
45
|
* sign-out revokes the token server-side (ADR 0031), so this is reusable across apps. Success
|
|
54
46
|
* is the app shell being replaced by the sign-in screen.
|
|
55
47
|
*/
|
|
56
|
-
export async function logout(page
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
48
|
+
export async function logout(page) {
|
|
49
|
+
await page.getByRole("button", { name: "Account menu" }).click();
|
|
50
|
+
await page.getByRole("menuitem", { name: "Sign out" }).click();
|
|
51
|
+
await expect(page.getByRole("heading", { name: "Sign in" })).toBeVisible();
|
|
60
52
|
}
|
|
53
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAa,MAAM,kBAAkB,CAAC;AAErD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,iBAAiB;IAC5D,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,uBAAuB,IAAI,8BAA8B;CAChF,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,qBAAqB,IAAI,kBAAkB;IAC9D,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,wBAAwB,IAAI,8BAA8B;CACjF,CAAC;AAEF,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,qBAAqB,IAAI,kBAAkB;IAC9D,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,wBAAwB,IAAI,8BAA8B;CACjF,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,KAAK,CACzB,IAAU,EACV,cAAmD,KAAK;IAExD,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACrB,MAAM,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IAC3E,MAAM,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IAC7D,MAAM,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;IACnE,MAAM,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;IAC5D,MAAM,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,EAAE;QAC1E,OAAO,EAAE,MAAM;KAChB,CAAC,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,IAAU;IACrC,MAAM,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;IACjE,MAAM,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;IAC/D,MAAM,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;AAC7E,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,20 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@terpjs/conformance",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Terp conformance \u2014 app-agnostic Playwright end-to-end flows over a running Terp stack (base profile: auth gating + session lifecycle) plus the login/role helpers an app's own e2e suite composes.",
|
|
6
|
-
"main": "./
|
|
7
|
-
"types": "./
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
8
|
"exports": {
|
|
9
|
-
".":
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
10
13
|
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
11
17
|
"scripts": {
|
|
18
|
+
"build": "tsc -p tsconfig.build.json",
|
|
19
|
+
"prepack": "npm run build",
|
|
20
|
+
"typecheck": "tsc --noEmit",
|
|
12
21
|
"test": "playwright test",
|
|
13
22
|
"report": "playwright show-report"
|
|
14
23
|
},
|
|
15
24
|
"devDependencies": {
|
|
16
25
|
"@playwright/test": "^1.50.0",
|
|
17
|
-
"@types/node": "^22.10.0"
|
|
26
|
+
"@types/node": "^22.10.0",
|
|
27
|
+
"typescript": "^5.9.3"
|
|
18
28
|
},
|
|
19
29
|
"license": "Apache-2.0",
|
|
20
30
|
"repository": {
|
package/playwright.config.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { defineConfig, devices } from "@playwright/test";
|
|
2
|
-
|
|
3
|
-
// The suite runs against an ALREADY-RUNNING Terp stack — the Docker workbench
|
|
4
|
-
// (`docker compose -f apps/example/docker-compose.yml up`) or `terp dev`. CI brings the stack
|
|
5
|
-
// up first; locally, point TERP_E2E_BASE_URL at your running frontend (e.g. the workbench port).
|
|
6
|
-
const baseURL = process.env.TERP_E2E_BASE_URL ?? "http://localhost:5173";
|
|
7
|
-
|
|
8
|
-
export default defineConfig({
|
|
9
|
-
testDir: "./tests",
|
|
10
|
-
fullyParallel: true,
|
|
11
|
-
forbidOnly: !!process.env.CI,
|
|
12
|
-
retries: process.env.CI ? 2 : 0,
|
|
13
|
-
reporter: process.env.CI ? [["list"], ["html", { open: "never" }]] : "list",
|
|
14
|
-
use: {
|
|
15
|
-
baseURL,
|
|
16
|
-
trace: "on-first-retry",
|
|
17
|
-
},
|
|
18
|
-
projects: [{ name: "chromium", use: { ...devices["Desktop Chrome"] } }],
|
|
19
|
-
});
|
package/tests/auth.spec.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { expect, test } from "@playwright/test";
|
|
2
|
-
|
|
3
|
-
import { ADMIN, login, logout } from "../src/index";
|
|
4
|
-
|
|
5
|
-
// Base-profile auth — reusable across ANY Terp app (the login screen + session are identical
|
|
6
|
-
// everywhere): a signed-out visitor is gated to the login screen, the seeded admin can sign in
|
|
7
|
-
// and reach the app shell, sign out again, and bad credentials are refused — all over the real
|
|
8
|
-
// deny-by-default backend, not a mock.
|
|
9
|
-
|
|
10
|
-
test("an unauthenticated visitor is gated to the sign-in screen", async ({ page }) => {
|
|
11
|
-
await page.goto("/");
|
|
12
|
-
await expect(page.getByRole("heading", { name: "Sign in" })).toBeVisible();
|
|
13
|
-
// The authenticated app shell (its primary navigation) is not reachable without a session.
|
|
14
|
-
await expect(page.getByRole("navigation", { name: "Primary" })).toHaveCount(0);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
test("the seeded admin can sign in and reach the app shell", async ({ page }) => {
|
|
18
|
-
await login(page, ADMIN);
|
|
19
|
-
await expect(page.getByRole("navigation", { name: "Primary" })).toBeVisible();
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
test("a signed-in session survives a page reload", async ({ page }) => {
|
|
23
|
-
await login(page, ADMIN);
|
|
24
|
-
await expect(page.getByRole("navigation", { name: "Primary" })).toBeVisible();
|
|
25
|
-
|
|
26
|
-
await page.reload();
|
|
27
|
-
|
|
28
|
-
// ADR 0054: the access token is still memory-only, but the httpOnly refresh cookie restores
|
|
29
|
-
// a fresh access token on boot, so a normal reload keeps the user in the app shell.
|
|
30
|
-
await expect(page.getByRole("navigation", { name: "Primary" })).toBeVisible();
|
|
31
|
-
await expect(page.getByRole("heading", { name: "Sign in" })).toHaveCount(0);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test("a signed-in user can sign out and is returned to the sign-in screen", async ({ page }) => {
|
|
35
|
-
await login(page, ADMIN);
|
|
36
|
-
await expect(page.getByRole("navigation", { name: "Primary" })).toBeVisible();
|
|
37
|
-
await logout(page);
|
|
38
|
-
// The session is gone: the app shell is no longer reachable, only the login screen.
|
|
39
|
-
await expect(page.getByRole("navigation", { name: "Primary" })).toHaveCount(0);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test("bad credentials are refused", async ({ page }) => {
|
|
43
|
-
await page.goto("/");
|
|
44
|
-
// A non-existent account, so a failed attempt never locks out the seeded admin.
|
|
45
|
-
await page.getByPlaceholder("Email").fill("nobody@acme.test");
|
|
46
|
-
await page.getByPlaceholder("Password").fill("definitely-not-the-password");
|
|
47
|
-
await page.getByRole("button", { name: "Sign in" }).click();
|
|
48
|
-
await expect(page.getByText("Sign-in failed")).toBeVisible();
|
|
49
|
-
await expect(page.getByRole("navigation", { name: "Primary" })).toHaveCount(0);
|
|
50
|
-
});
|
package/tests/standard.spec.ts
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { expect, test, type APIRequestContext } from "@playwright/test";
|
|
2
|
-
|
|
3
|
-
import { ADMIN } from "../src/index";
|
|
4
|
-
|
|
5
|
-
// Terp Standard black-box probes (spec/catalog entries with layer "black-box"): these
|
|
6
|
-
// invariants are observable on a RUNNING app with no source access, so they are portable to
|
|
7
|
-
// any stack — this file is the reference realisation the catalog's `black-box` enforcement
|
|
8
|
-
// entries point at. Like auth.spec.ts, the probes use only the base profile (auth + the
|
|
9
|
-
// seeded admin) plus the `users` capability every generated app starts with, so they stay
|
|
10
|
-
// app-agnostic. Each test title is the catalog entry's enforcement `ref`.
|
|
11
|
-
|
|
12
|
-
async function bearer(request: APIRequestContext): Promise<{ Authorization: string }> {
|
|
13
|
-
const response = await request.post("/api/v1/auth/login", { data: ADMIN });
|
|
14
|
-
expect(response.ok()).toBe(true);
|
|
15
|
-
const { access_token } = (await response.json()) as { access_token: string };
|
|
16
|
-
return { Authorization: "Bearer " + access_token };
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// spec/catalog/backend/list_routes_paginate.json
|
|
20
|
-
test("standard: list routes return a capped Page envelope", async ({ request }) => {
|
|
21
|
-
const headers = await bearer(request);
|
|
22
|
-
|
|
23
|
-
// A collection GET is a uniform Page envelope, never a bare JSON array.
|
|
24
|
-
const page = await request.get("/api/v1/users/", { headers });
|
|
25
|
-
expect(page.ok()).toBe(true);
|
|
26
|
-
const body = (await page.json()) as Record<string, unknown>;
|
|
27
|
-
expect(Array.isArray(body)).toBe(false);
|
|
28
|
-
expect(body).toMatchObject({ items: expect.any(Array), total: expect.any(Number) });
|
|
29
|
-
expect(typeof body.limit).toBe("number");
|
|
30
|
-
expect(typeof body.skip).toBe("number");
|
|
31
|
-
|
|
32
|
-
// The page size is capped fail-closed: an oversized limit is refused, not honoured.
|
|
33
|
-
const oversized = await request.get("/api/v1/users/?limit=100000", { headers });
|
|
34
|
-
expect(oversized.status()).toBe(422);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
// spec/catalog/backend/safe_methods_are_read_only.json
|
|
38
|
-
test("standard: safe methods observably mutate nothing", async ({ request }) => {
|
|
39
|
-
const headers = await bearer(request);
|
|
40
|
-
|
|
41
|
-
// Reading a collection is repeatable: the GETs themselves change no state, so the
|
|
42
|
-
// second read reports exactly the population the first one saw.
|
|
43
|
-
const first = await request.get("/api/v1/users/", { headers });
|
|
44
|
-
expect(first.ok()).toBe(true);
|
|
45
|
-
const before = (await first.json()) as { total: number; items: unknown[] };
|
|
46
|
-
|
|
47
|
-
const second = await request.get("/api/v1/users/", { headers });
|
|
48
|
-
expect(second.ok()).toBe(true);
|
|
49
|
-
const after = (await second.json()) as { total: number; items: unknown[] };
|
|
50
|
-
|
|
51
|
-
expect(after.total).toBe(before.total);
|
|
52
|
-
expect(after.items.length).toBe(before.items.length);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
// spec/catalog/backend/schemas_exclude_sensitive_fields.json
|
|
56
|
-
const SENSITIVE_KEY = new RegExp(
|
|
57
|
-
"(?:^|_)(?:password|passwd|pwd|passphrase|secret|salt|api_key|apikey" +
|
|
58
|
-
"|private_key|privatekey|credentials?)(?:$|_)|(?:^|_)token$",
|
|
59
|
-
);
|
|
60
|
-
const SENSITIVE_KEY_EXCLUSIONS = new Set(["token_version", "version"]);
|
|
61
|
-
|
|
62
|
-
function sensitiveKeys(value: unknown, found: string[] = []): string[] {
|
|
63
|
-
if (Array.isArray(value)) {
|
|
64
|
-
value.forEach((item) => sensitiveKeys(item, found));
|
|
65
|
-
} else if (value !== null && typeof value === "object") {
|
|
66
|
-
for (const [key, nested] of Object.entries(value)) {
|
|
67
|
-
if (!SENSITIVE_KEY_EXCLUSIONS.has(key.toLowerCase()) && SENSITIVE_KEY.test(key.toLowerCase())) {
|
|
68
|
-
found.push(key);
|
|
69
|
-
}
|
|
70
|
-
sensitiveKeys(nested, found);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return found;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
test("standard: responses never expose credential-shaped fields", async ({ request }) => {
|
|
77
|
-
const headers = await bearer(request);
|
|
78
|
-
for (const path of ["/api/v1/users/", "/api/v1/me/"]) {
|
|
79
|
-
const response = await request.get(path, { headers });
|
|
80
|
-
expect(response.ok()).toBe(true);
|
|
81
|
-
expect(sensitiveKeys(await response.json()), `${path} leaked a credential-shaped field`).toEqual(
|
|
82
|
-
[],
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
});
|
package/tsconfig.json
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"moduleResolution": "Bundler",
|
|
6
|
-
"types": ["node"],
|
|
7
|
-
"strict": true,
|
|
8
|
-
"noEmit": true,
|
|
9
|
-
"esModuleInterop": true,
|
|
10
|
-
"skipLibCheck": true,
|
|
11
|
-
"forceConsistentCasingInFileNames": true
|
|
12
|
-
},
|
|
13
|
-
"include": ["src", "tests", "playwright.config.ts"]
|
|
14
|
-
}
|