Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 57 additions & 74 deletions Jenkinsfile

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion packages/web/e2e/access-control.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
// is refused the /admin console in-page.
import { test, expect } from "@playwright/test";
import { signIn } from "./helpers/auth";
import { uniqueStamp } from "./helpers/stamp";

test("access control: an unauthenticated visitor is redirected to the sign-in landing", async ({ browser }) => {
const ctx = await browser.newContext(); // no sign-in
Expand Down Expand Up @@ -33,7 +34,7 @@ test("sign-in landing: signed-out Home shows the Entra button, no nav links, and
});

test("access control: a non-admin member is refused the Administration console", async ({ browser }) => {
const stamp = Date.now().toString(36);
const stamp = uniqueStamp();
const ctx = await browser.newContext();
await signIn(ctx, { name: `E2E Member ${stamp}` }); // admin flag off → plain member
const page = await ctx.newPage();
Expand Down
3 changes: 2 additions & 1 deletion packages/web/e2e/admin-ai.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import http from "node:http";
import type { AddressInfo } from "node:net";
import { test, expect } from "@playwright/test";
import { signIn } from "./helpers/auth";
import { uniqueStamp } from "./helpers/stamp";

const TOKEN = "sk-e2e-stub-key-0042";

Expand Down Expand Up @@ -45,7 +46,7 @@ function startStub(): Promise<{ base: string; close: () => Promise<void> }> {
}

test("AI provider: platform-admin only; configure stub → load models → test → save → enable → failing save → remove", async ({ browser }) => {
const stamp = Date.now().toString(36);
const stamp = uniqueStamp();
const base = process.env.E2E_BASE_URL || "http://localhost:3000";

const memberCtx = await browser.newContext();
Expand Down
3 changes: 2 additions & 1 deletion packages/web/e2e/admin-audit-verify.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
// under the Admin chip; a plain member is refused (403) at the API.
import { test, expect } from "@playwright/test";
import { signIn } from "./helpers/auth";
import { uniqueStamp } from "./helpers/stamp";

test("audit integrity: an admin verifies the chain and the run is audited", async ({ browser }) => {
const stamp = Date.now().toString(36);
const stamp = uniqueStamp();

const memberCtx = await browser.newContext();
await signIn(memberCtx, { name: `E2E Verify Member ${stamp}` });
Expand Down
3 changes: 2 additions & 1 deletion packages/web/e2e/admin-audit.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
import { test, expect } from "@playwright/test";
import { signIn } from "./helpers/auth";
import { createChallengeViaApi, setChallengeStatusViaApi } from "./helpers/api";
import { uniqueStamp } from "./helpers/stamp";

test("audit log: an audited status change is filterable and its before/after is inspectable", async ({ browser }) => {
const stamp = Date.now().toString(36);
const stamp = uniqueStamp();

// A member raises a challenge; the admin makes a distinctive transition (the audited action).
const memberCtx = await browser.newContext();
Expand Down
3 changes: 2 additions & 1 deletion packages/web/e2e/admin-banner.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
// only — a member's direct API call is refused.
import { test, expect } from "@playwright/test";
import { signIn } from "./helpers/auth";
import { uniqueStamp } from "./helpers/stamp";

test("system banner: publish → everyone sees it → replace → clear", async ({ browser }) => {
const stamp = Date.now().toString(36);
const stamp = uniqueStamp();
const base = process.env.E2E_BASE_URL || "http://localhost:3000";

const adminCtx = await browser.newContext();
Expand Down
7 changes: 4 additions & 3 deletions packages/web/e2e/admin-delete.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@
import { test, expect } from "@playwright/test";
import { signIn } from "./helpers/auth";
import { createChallengeViaApi, proposeSolutionViaApi, setChallengeStatusViaApi, setSolutionStatusViaApi } from "./helpers/api";
import { uniqueStamp } from "./helpers/stamp";

test("a member never sees the danger zone, and their DELETE is a 404 that changes nothing", async ({ browser }) => {
const stamp = Date.now().toString(36);
const stamp = uniqueStamp();

const memberCtx = await browser.newContext();
await signIn(memberCtx, { name: `E2E Delete Member ${stamp}` });
Expand Down Expand Up @@ -41,7 +42,7 @@ test("a member never sees the danger zone, and their DELETE is a 404 that change
});

test("the danger zone arms only on the typed number plus a reason, then deletes for good", async ({ browser }) => {
const stamp = Date.now().toString(36);
const stamp = uniqueStamp();

const adminCtx = await browser.newContext();
await signIn(adminCtx, { name: "E2E Delete Admin", admin: true });
Expand Down Expand Up @@ -115,7 +116,7 @@ test("the danger zone arms only on the typed number plus a reason, then deletes
});

test("a blank reason is refused by the API", async ({ browser }) => {
const stamp = Date.now().toString(36);
const stamp = uniqueStamp();

const adminCtx = await browser.newContext();
await signIn(adminCtx, { name: "E2E Delete Admin", admin: true });
Expand Down
3 changes: 2 additions & 1 deletion packages/web/e2e/admin-erasure.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@
import { test, expect } from "@playwright/test";
import { signIn } from "./helpers/auth";
import { createChallengeViaApi, createNamespaceViaApi, setChallengeStatusViaApi } from "./helpers/api";
import { uniqueStamp } from "./helpers/stamp";

test("erasing a user hands visible open assignments to the chosen successor and lists the rest", async ({ browser }) => {
const stamp = Date.now().toString(36);
const stamp = uniqueStamp();
const base = process.env.E2E_BASE_URL || "http://localhost:3000";
const json = { "content-type": "application/json", accept: "application/json" };

Expand Down
5 changes: 3 additions & 2 deletions packages/web/e2e/admin-identity-sync.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
// admins, and the counts depend on SCIM-written rows the e2e stack does not provision.
import { test, expect } from "@playwright/test";
import { signIn } from "./helpers/auth";
import { uniqueStamp } from "./helpers/stamp";

test("identity sync: platform-admin only — a member sees no card and gets 403 from the API", async ({ browser }) => {
const stamp = Date.now().toString(36);
const stamp = uniqueStamp();
const memberCtx = await browser.newContext();
await signIn(memberCtx, { name: `E2E Idsync Member ${stamp}` });

Expand All @@ -23,7 +24,7 @@ test("identity sync: platform-admin only — a member sees no card and gets 403
});

test("identity sync: the card shows counts, request times, the never-arrived list and a summary chip", async ({ browser }) => {
const stamp = Date.now().toString(36);
const stamp = uniqueStamp();
const adminCtx = await browser.newContext();
await signIn(adminCtx, { name: `E2E Idsync Admin ${stamp}`, admin: true });

Expand Down
7 changes: 4 additions & 3 deletions packages/web/e2e/admin-presence.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
// last_route, which the 60-second write throttle makes unreliable to stage through the UI.
import { test, expect } from "@playwright/test";
import { signIn } from "./helpers/auth";
import { uniqueStamp } from "./helpers/stamp";

test("presence panel: platform-admin only, and a namespace-less member gets 403 from the API", async ({ browser }) => {
const stamp = Date.now().toString(36);
const stamp = uniqueStamp();

const memberCtx = await browser.newContext();
await signIn(memberCtx, { name: `E2E Presence Member ${stamp}` });
Expand All @@ -29,7 +30,7 @@ test("presence panel: platform-admin only, and a namespace-less member gets 403
});

test("presence panel: lists who is active, with the window, search and Refresh — and no Reach out", async ({ browser }) => {
const stamp = Date.now().toString(36);
const stamp = uniqueStamp();

// A member whose sign-in lands them on the dashboard — an allowlisted request, so they are
// located at "Overview". This is the allowlist working end to end.
Expand Down Expand Up @@ -109,7 +110,7 @@ test("presence panel: lists who is active, with the window, search and Refresh
});

test("presence panel: opening it writes one presence.view audit row (§14.5, §15)", async ({ browser }) => {
const stamp = Date.now().toString(36);
const stamp = uniqueStamp();
const adminCtx = await browser.newContext();
const { userId } = await signIn(adminCtx, { name: `E2E Presence Auditor ${stamp}`, admin: true });

Expand Down
9 changes: 5 additions & 4 deletions packages/web/e2e/admin-settings.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import { test, expect, type Page, type Locator } from "@playwright/test";
import { signIn } from "./helpers/auth";
import { createChallengeViaApi, getImpactAreaId } from "./helpers/api";
import { uniqueStamp } from "./helpers/stamp";

// Impact-area rows all share the aria-label "Impact area name" (exact, to exclude the "New impact
// area name" create box), so a row is identified by its input's current value.
Expand All @@ -29,7 +30,7 @@ async function impactAreaRow(page: Page, value: string): Promise<Locator> {
}

test("platform settings are refused to a non-platform-admin", async ({ browser }) => {
const stamp = Date.now().toString(36);
const stamp = uniqueStamp();
const ctx = await browser.newContext();
await signIn(ctx, { name: `E2E Settings Member ${stamp}` }); // plain member
const page = await ctx.newPage();
Expand All @@ -40,7 +41,7 @@ test("platform settings are refused to a non-platform-admin", async ({ browser }

test("impact areas: create, retire, then delete a zero-reference area", async ({ browser }) => {
test.slow(); // first hit to each of the create/retire/delete impact-area routes compiles cold
const stamp = Date.now().toString(36);
const stamp = uniqueStamp();
const name = `E2E ZeroRef Area ${stamp}`;

const ctx = await browser.newContext();
Expand Down Expand Up @@ -74,7 +75,7 @@ test("impact areas: create, retire, then delete a zero-reference area", async ({

test("impact areas: a retired area with challenges is deleted only after reassignment", async ({ browser }) => {
test.slow(); // create + submit + retire + delete each may hit a cold-compiled route
const stamp = Date.now().toString(36);
const stamp = uniqueStamp();
const name = `E2E RefArea ${stamp}`;

const ctx = await browser.newContext();
Expand Down Expand Up @@ -114,7 +115,7 @@ test("impact areas: a retired area with challenges is deleted only after reassig
});

test("date format: switching between EU and US persists across a reload", async ({ browser }) => {
const stamp = Date.now().toString(36);
const stamp = uniqueStamp();
const ctx = await browser.newContext();
await signIn(ctx, { name: `E2E DateFmt Admin ${stamp}`, admin: true });
const page = await ctx.newPage();
Expand Down
3 changes: 2 additions & 1 deletion packages/web/e2e/admin-system-log.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
// locked out of the page itself.
import { test, expect } from "@playwright/test";
import { signIn } from "./helpers/auth";
import { uniqueStamp } from "./helpers/stamp";

test("system log: a member's 403 is recorded and visible to a platform admin", async ({ browser }) => {
const stamp = Date.now().toString(36);
const stamp = uniqueStamp();
const base = process.env.E2E_BASE_URL || "http://localhost:3000";

const memberCtx = await browser.newContext();
Expand Down
5 changes: 3 additions & 2 deletions packages/web/e2e/admin-trash.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@
import { test, expect } from "@playwright/test";
import { signIn } from "./helpers/auth";
import { createChallengeViaApi, setChallengeStatusViaApi } from "./helpers/api";
import { uniqueStamp } from "./helpers/stamp";

test("a member never sees Move to trash, and the trash endpoints answer them 404", async ({ browser }) => {
const stamp = Date.now().toString(36);
const stamp = uniqueStamp();
const memberCtx = await browser.newContext();
await signIn(memberCtx, { name: `E2E Trash Member ${stamp}` });
const challenge = await createChallengeViaApi(memberCtx.request, {
Expand All @@ -35,7 +36,7 @@ test("a member never sees Move to trash, and the trash endpoints answer them 404
});

test("move a challenge to the trash, see it gone from the list, restore it from the trash page", async ({ browser }) => {
const stamp = Date.now().toString(36);
const stamp = uniqueStamp();
const title = `E2E trash-me challenge ${stamp}`;

const authorCtx = await browser.newContext();
Expand Down
5 changes: 3 additions & 2 deletions packages/web/e2e/admin-triage.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
import { test, expect } from "@playwright/test";
import { signIn } from "./helpers/auth";
import { createChallengeViaApi } from "./helpers/api";
import { uniqueStamp } from "./helpers/stamp";

test("triage queue: filter to a challenge by author, navigate to it, and switch tabs", async ({ browser }) => {
const stamp = Date.now().toString(36);
const stamp = uniqueStamp();
const memberName = `E2E Queue Member ${stamp}`;
const title = `E2E triage-queue challenge ${stamp}`;

Expand Down Expand Up @@ -45,7 +46,7 @@ test("triage queue: filter to a challenge by author, navigate to it, and switch
});

test("triage queue: the CSV export is offered and produces a CSV", async ({ browser }) => {
const stamp = Date.now().toString(36);
const stamp = uniqueStamp();
const adminCtx = await browser.newContext();
await signIn(adminCtx, { name: `E2E Export Admin ${stamp}`, admin: true });
const admin = await adminCtx.newPage();
Expand Down
3 changes: 2 additions & 1 deletion packages/web/e2e/admin-webhooks.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
// public IP literal, so saving needs no DNS; no Send test is run (it would leave the machine).
import { test, expect } from "@playwright/test";
import { signIn } from "./helpers/auth";
import { uniqueStamp } from "./helpers/stamp";

test("channel webhooks: platform-admin only; add → hint only → switch off → edit keeps URL → delete", async ({ browser }) => {
const stamp = Date.now().toString(36);
const stamp = uniqueStamp();
const base = process.env.E2E_BASE_URL || "http://localhost:3000";

const memberCtx = await browser.newContext();
Expand Down
3 changes: 2 additions & 1 deletion packages/web/e2e/agent-access.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import { createHash, randomBytes } from "node:crypto";
import { test, expect, type APIRequestContext } from "@playwright/test";
import { signIn } from "./helpers/auth";
import { uniqueStamp } from "./helpers/stamp";

const base = () => process.env.E2E_BASE_URL || "http://localhost:3000";
const REDIRECT = "http://127.0.0.1:59999/callback";
Expand All @@ -19,7 +20,7 @@ async function mcpCall(request: APIRequestContext, token: string, method: string
}

test("connect from /agents through consent; the agent's challenge shows via agent; revoke stops it", async ({ browser, playwright }) => {
const stamp = Date.now().toString(36);
const stamp = uniqueStamp();
const clientName = `E2E Agent ${stamp}`;
const adminCtx = await browser.newContext();
const personCtx = await browser.newContext();
Expand Down
3 changes: 2 additions & 1 deletion packages/web/e2e/ai-draft.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import { signIn } from "./helpers/auth";
import { openNewChallengeForm } from "./helpers/forms";
import { createChallengeViaApi } from "./helpers/api";
import { aiKeyPresent, disableAiStub, enableAiStub, startAiStub } from "./helpers/ai-stub";
import { uniqueStamp } from "./helpers/stamp";

test("Improve with AI: confirm, replace, hint, undo, duplicate check re-armed", async ({ browser }) => {
const stamp = Date.now().toString(36);
const stamp = uniqueStamp();
const base = process.env.E2E_BASE_URL || "http://localhost:3000";

const adminCtx = await browser.newContext();
Expand Down
3 changes: 2 additions & 1 deletion packages/web/e2e/ai-similar.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import { signIn } from "./helpers/auth";
import { openNewChallengeForm } from "./helpers/forms";
import { createChallengeViaApi } from "./helpers/api";
import { aiKeyPresent, disableAiStub, enableAiStub, startAiStub } from "./helpers/ai-stub";
import { uniqueStamp } from "./helpers/stamp";

test("similar challenges, explained: reasons in the duplicate banner", async ({ browser }) => {
const stamp = Date.now().toString(36);
const stamp = uniqueStamp();
const base = process.env.E2E_BASE_URL || "http://localhost:3000";

const adminCtx = await browser.newContext();
Expand Down
3 changes: 2 additions & 1 deletion packages/web/e2e/ai-summary.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import { test, expect } from "@playwright/test";
import { signIn } from "./helpers/auth";
import { createChallengeViaApi } from "./helpers/api";
import { aiKeyPresent, disableAiStub, enableAiStub, startAiStub } from "./helpers/ai-stub";
import { uniqueStamp } from "./helpers/stamp";

test("discussion summary: summarize a long thread, then it goes stale", async ({ browser }) => {
const stamp = Date.now().toString(36);
const stamp = uniqueStamp();
const base = process.env.E2E_BASE_URL || "http://localhost:3000";

const adminCtx = await browser.newContext();
Expand Down
3 changes: 2 additions & 1 deletion packages/web/e2e/ai-triage.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import { test, expect } from "@playwright/test";
import { signIn } from "./helpers/auth";
import { createChallengeViaApi } from "./helpers/api";
import { aiKeyPresent, disableAiStub, enableAiStub, startAiStub } from "./helpers/ai-stub";
import { uniqueStamp } from "./helpers/stamp";

test("triage assist: admins see the pending note and the queue indicator; the author never does", async ({ browser }) => {
const stamp = Date.now().toString(36);
const stamp = uniqueStamp();
const base = process.env.E2E_BASE_URL || "http://localhost:3000";

const adminCtx = await browser.newContext();
Expand Down
3 changes: 2 additions & 1 deletion packages/web/e2e/anonymity.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
import { test, expect } from "@playwright/test";
import { signIn } from "./helpers/auth";
import { openNewChallengeForm, submitNewChallenge } from "./helpers/forms";
import { uniqueStamp } from "./helpers/stamp";

test("anonymity: an anonymous challenge is masked as 'Anonymous', even to its author", async ({ browser }) => {
const stamp = Date.now().toString(36);
const stamp = uniqueStamp();
const authorName = `E2E Anon Author ${stamp}`;
const title = `E2E anonymous challenge ${stamp}`;

Expand Down
9 changes: 5 additions & 4 deletions packages/web/e2e/attachment-drop.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import { test, expect, type Page } from "@playwright/test";
import { signIn } from "./helpers/auth";
import { createChallengeViaApi, proposeSolutionViaApi, setChallengeStatusViaApi } from "./helpers/api";
import { uniqueStamp } from "./helpers/stamp";

interface Posted {
name: string;
Expand Down Expand Up @@ -81,7 +82,7 @@ async function paste(page: Page, opts: { files: { name: string; type: string }[]
}

test("challenge form: the whole page takes a drop — pre-checks, a sequential queue, Submit gated", async ({ browser }) => {
const stamp = Date.now().toString(36);
const stamp = uniqueStamp();
const ctx = await browser.newContext();
await signIn(ctx, { name: `E2E Drop Author ${stamp}` });
const page = await ctx.newPage();
Expand Down Expand Up @@ -115,7 +116,7 @@ test("challenge form: the whole page takes a drop — pre-checks, a sequential q
});

test("challenge form: a file-only paste attaches (renamed); a paste with text stays text", async ({ browser }) => {
const stamp = Date.now().toString(36);
const stamp = uniqueStamp();
const ctx = await browser.newContext();
await signIn(ctx, { name: `E2E Paste Author ${stamp}` });
const page = await ctx.newPage();
Expand All @@ -139,7 +140,7 @@ test("challenge form: a file-only paste attaches (renamed); a paste with text st
});

test("detail page: one overlay target per control; drops and pastes go where they're aimed", async ({ browser }) => {
const stamp = Date.now().toString(36);
const stamp = uniqueStamp();
const ctx = await browser.newContext();
await signIn(ctx, { name: `E2E Drop Admin ${stamp}`, admin: true });
const challenge = await createChallengeViaApi(ctx.request, { title: `Drop targets ${stamp}`, description: "Where do dropped files go?" });
Expand Down Expand Up @@ -176,7 +177,7 @@ test("detail page: one overlay target per control; drops and pastes go where the
});

test("a file drag with no available control raises no overlay but never lets the browser open the file", async ({ browser }) => {
const stamp = Date.now().toString(36);
const stamp = uniqueStamp();
const ctx = await browser.newContext();
await signIn(ctx, { name: `E2E Drop Viewer ${stamp}`, admin: true });
const challenge = await createChallengeViaApi(ctx.request, { title: `No targets ${stamp}`, description: "Nothing editable here." });
Expand Down
Loading
Loading