mirror of
https://github.com/civitai/civitai.git
synced 2026-09-20 22:08:18 +08:00
2cc4432833
* test(preview): tranche-2 e2e — generation whatIf, report loop, moderation
Extends the preview smoke harness to the audit's highest-risk untested flows,
self-seeding per-run uniquely-tagged fixtures (no shared-dev-DB collisions; the
mutation isolation answer to "seed per-PR"). All report-only.
- preview-trpc.ts: tiny superjson+batched tRPC client (CSRF Origin/Referer
stamped) for self-seeding via page.request, + uniqueToken().
- preview-generation.spec.ts: assert the REAL orchestrator.whatIfFromGraph cost
quote fires + returns a numeric cost (gold). De-mocks the pricing path; no Buzz.
- preview-report.spec.ts: tester self-seeds a Post (post.create) and reports it
(report.create {type:'post',reason:'TOSViolation',details:{violation}}).
- preview-moderation.spec.ts: mod queues (/moderator/reports + /images) render;
+ a self-seeded report is actioned end-to-end (report.create -> setStatus).
tRPC input shapes verified against the schema files. NOT YET live-validated —
the CSRF origin gate on direct tRPC + the whatIf/mod selectors get confirmed by
the next report-only preview run (deferred: generation-submit, blocked on the
external Buzz service which can't be seeded via Postgres).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(preview): typecheck — reportId is number | undefined
the pipeline tsc (not playwright --list) caught: reportId inferred number
from report?.id, then assigned mine?.id (number | undefined) -> TS2322.
Type it explicitly; the runtime expect() still guards undefined.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(preview): drop the two fragile secondary DOM checks
The live run validated every core tranche-2 flow (real whatIf cost, tRPC
self-seed report round-trip through the CSRF gate, mod render + report->action
loop). The only two reds were optional best-effort DOM checks that duplicate
passing core tests:
- generation "cost near submit button": the submit button is in a gen panel
collapsed by default on the preview viewport (resolves but hidden). The
network whatIf assertion already covers the cost.
- report "image page report affordance": the report control is behind an entity
action menu, not a top-level button. report.create is already covered.
Removed both rather than leave known flakes in the gate.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
80 lines
3.1 KiB
TypeScript
80 lines
3.1 KiB
TypeScript
import type { APIRequestContext } from '@playwright/test';
|
|
|
|
/**
|
|
* Minimal tRPC client for the preview e2e tests, used to SELF-SEED uniquely-tagged
|
|
* fixtures (e.g. a Post to report) so mutation tests don't collide on the shared
|
|
* dev-DB across concurrent previews. NOT a test file (excluded from testMatch).
|
|
*
|
|
* civitai is tRPC v11 with the **superjson** transformer and batching (src/server/
|
|
* trpc.ts, src/utils/trpc.ts). We mirror the real client's batched wire format:
|
|
* POST /api/trpc/<proc>?batch=1 body {"0":{"json":<input>}}
|
|
* GET /api/trpc/<proc>?batch=1&input=<urlenc({"0":{"json":<input>}})>
|
|
* response: [{ result: { data: { json: <output> } } }]
|
|
*
|
|
* Two server-side gotchas the recon surfaced:
|
|
* - CSRF/origin gate (src/server/createContext.ts): a cookie-authed request is
|
|
* rejected unless its Origin/Referer host is allowlisted. The preview's own
|
|
* host is (NEXTAUTH_URL = the preview URL), so we stamp Origin+Referer.
|
|
* - guardedProcedure (post/report create) needs onboarding complete + not muted;
|
|
* the ci-smoke fixtures are seeded with onboarding=15, so they pass.
|
|
*
|
|
* Pass `page.request` (which carries the test's storageState auth cookie) as the
|
|
* `request` arg from inside a test that set `storageState`.
|
|
*/
|
|
|
|
const PREVIEW_URL = process.env.PREVIEW_URL ?? '';
|
|
|
|
function csrfHeaders(): Record<string, string> {
|
|
return { origin: PREVIEW_URL, referer: `${PREVIEW_URL}/` };
|
|
}
|
|
|
|
function unwrap(body: unknown, proc: string): unknown {
|
|
const entry = Array.isArray(body) ? (body as unknown[])[0] : body;
|
|
const e = entry as { error?: unknown; result?: { data?: { json?: unknown } } };
|
|
if (e?.error) {
|
|
throw new Error(`tRPC ${proc} returned error: ${JSON.stringify(e.error).slice(0, 400)}`);
|
|
}
|
|
return e?.result?.data?.json;
|
|
}
|
|
|
|
export async function trpcMutation<T = unknown>(
|
|
request: APIRequestContext,
|
|
proc: string,
|
|
input: unknown
|
|
): Promise<T> {
|
|
const res = await request.post(`/api/trpc/${proc}?batch=1`, {
|
|
headers: { 'content-type': 'application/json', ...csrfHeaders() },
|
|
data: { '0': { json: input } },
|
|
});
|
|
const body = await res.json().catch(() => ({}));
|
|
if (!res.ok()) {
|
|
throw new Error(`tRPC mutation ${proc} -> HTTP ${res.status()}: ${JSON.stringify(body).slice(0, 400)}`);
|
|
}
|
|
return unwrap(body, proc) as T;
|
|
}
|
|
|
|
export async function trpcQuery<T = unknown>(
|
|
request: APIRequestContext,
|
|
proc: string,
|
|
input?: unknown
|
|
): Promise<T> {
|
|
const enc = encodeURIComponent(JSON.stringify({ '0': { json: input ?? {} } }));
|
|
const res = await request.get(`/api/trpc/${proc}?batch=1&input=${enc}`, {
|
|
headers: csrfHeaders(),
|
|
});
|
|
const body = await res.json().catch(() => ({}));
|
|
if (!res.ok()) {
|
|
throw new Error(`tRPC query ${proc} -> HTTP ${res.status()}: ${JSON.stringify(body).slice(0, 400)}`);
|
|
}
|
|
return unwrap(body, proc) as T;
|
|
}
|
|
|
|
/**
|
|
* Per-run unique tag, stamped into a seeded entity's free-text field so the test
|
|
* can find exactly its own fixture (and concurrent previews never collide).
|
|
*/
|
|
export function uniqueToken(label: string): string {
|
|
const rand = Math.random().toString(36).slice(2, 10);
|
|
return `e2e-${label}-${Date.now()}-${rand}`;
|
|
}
|