From 7a66c05b63883a248e95cf486f304aafffddb60e Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 2 Oct 2026 20:43:35 +0000 Subject: [PATCH 1/9] test(db): drop pgTAP checks that only prove an object exists Removes 32 has_table, has_function and has_column assertions from six pgTAP files. A behavioral assertion in the same suite already reads, writes or calls each object, so it fails if the object goes missing. Grants, RLS, trigger and constraint checks stay. Plans go from 451 to 419. Co-Authored-By: Claude Opus 5.5 (1M context) --- supabase/tests/daily_usage_quota_test.sql | 8 +---- .../tests/workspace_collaboration_test.sql | 34 +------------------ supabase/tests/workspace_invites_test.sql | 4 +-- supabase/tests/workspace_practice_test.sql | 12 +------ supabase/tests/workspace_review_test.sql | 11 +----- supabase/tests/workspaces_rls_test.sql | 7 +--- 6 files changed, 6 insertions(+), 70 deletions(-) diff --git a/supabase/tests/daily_usage_quota_test.sql b/supabase/tests/daily_usage_quota_test.sql index 7219d4070..14cb94513 100644 --- a/supabase/tests/daily_usage_quota_test.sql +++ b/supabase/tests/daily_usage_quota_test.sql @@ -1,17 +1,11 @@ BEGIN; -SELECT plan(15); +SELECT plan(14); INSERT INTO auth.users (id) VALUES ('44444444-4444-4444-8444-444444444444'), ('55555555-5555-4555-8555-555555555555'); -SELECT has_column( - 'public', - 'daily_usage_counters', - 'quota_key', - 'daily usage counters identify the quota being consumed' -); SELECT col_not_null( 'public', 'daily_usage_counters', diff --git a/supabase/tests/workspace_collaboration_test.sql b/supabase/tests/workspace_collaboration_test.sql index ff475bc48..02c446add 100644 --- a/supabase/tests/workspace_collaboration_test.sql +++ b/supabase/tests/workspace_collaboration_test.sql @@ -1,44 +1,12 @@ BEGIN; -SELECT plan(19); +SELECT plan(12); -SELECT has_table( - 'private', - 'workspace_document_state', - 'canonical workspace document state is private' -); -SELECT has_table( - 'private', - 'workspace_document_updates', - 'durable workspace updates are private' -); -SELECT has_function( - 'public', - 'create_workspace_document', - ARRAY['uuid', 'text', 'text', 'uuid', 'bigint', 'uuid'], - 'members create or duplicate files through one transactional RPC' -); SELECT has_trigger( 'public', 'workspace_members', 'workspace_members_notify_access_revoked_after_delete', 'member removal notifies the collaboration fence after commit' ); -SELECT has_table('public', 'workspace_versions', 'version metadata is member-readable'); -SELECT has_function( - 'public', 'create_workspace_version', - ARRAY['uuid', 'uuid', 'text', 'bigint', 'uuid'], - 'members create named or automatic versions through one RPC' -); -SELECT has_function( - 'public', 'restore_workspace_version', - ARRAY['uuid', 'uuid', 'uuid', 'bigint', 'uuid'], - 'restore compares the previewed sequence through one RPC' -); -SELECT has_function( - 'public', 'get_workspace_version_snapshot', - ARRAY['uuid', 'uuid', 'uuid'], - 'preview returns private version bytes only through an authorized RPC' -); INSERT INTO auth.users (id) VALUES ('71111111-1111-4111-8111-111111111111'), diff --git a/supabase/tests/workspace_invites_test.sql b/supabase/tests/workspace_invites_test.sql index 90327e009..03b5780a7 100644 --- a/supabase/tests/workspace_invites_test.sql +++ b/supabase/tests/workspace_invites_test.sql @@ -1,5 +1,5 @@ BEGIN; -SELECT plan(48); +SELECT plan(46); INSERT INTO auth.users (id, email, email_confirmed_at) VALUES @@ -25,8 +25,6 @@ SELECT set_config( INSERT INTO public.workspace_members (workspace_id, user_id) VALUES (current_setting('test.workspace_id')::uuid, '62222222-2222-4222-8222-222222222222'); -SELECT has_table('private', 'workspace_invites', 'private workspace invites exist'); -SELECT has_table('private', 'workspace_invite_attempts', 'private redemption attempts exist'); SELECT ok( NOT has_table_privilege('anon', 'private.workspace_invites', 'SELECT,INSERT,UPDATE,DELETE') AND NOT has_table_privilege('authenticated', 'private.workspace_invites', 'SELECT,INSERT,UPDATE,DELETE') diff --git a/supabase/tests/workspace_practice_test.sql b/supabase/tests/workspace_practice_test.sql index ac48de43d..c42820f50 100644 --- a/supabase/tests/workspace_practice_test.sql +++ b/supabase/tests/workspace_practice_test.sql @@ -1,16 +1,6 @@ BEGIN; -SELECT plan(32); +SELECT plan(22); -SELECT has_table('public', 'workspace_sources', 'shared sources exist'); -SELECT has_table('public', 'workspace_threads', 'shared threads exist'); -SELECT has_table('public', 'workspace_thread_messages', 'thread messages exist'); -SELECT has_table('public', 'workspace_artifacts', 'practice artifacts exist'); -SELECT has_function('public', 'save_workspace_source', ARRAY['uuid','uuid','text','text','text','jsonb','jsonb'], 'sources use an authorized RPC'); -SELECT has_function('public', 'create_workspace_thread', ARRAY['uuid','uuid','text'], 'threads use an authorized RPC'); -SELECT has_function('public', 'save_workspace_thread_message', ARRAY['uuid','uuid','uuid','uuid','text','text','text'], 'messages use an authorized RPC'); -SELECT has_function('public', 'start_workspace_thread_generation', ARRAY['uuid','uuid','uuid','uuid','uuid','uuid','text'], 'thread generation starts atomically'); -SELECT has_function('public', 'complete_workspace_thread_generation', ARRAY['uuid','uuid','uuid','uuid','text','text'], 'assistant completion stays server-owned'); -SELECT has_function('public', 'save_workspace_artifact', ARRAY['uuid','uuid','uuid','uuid','bigint','text','text','jsonb','jsonb','jsonb'], 'artifacts use an authorized RPC'); SELECT ok( NOT has_function_privilege('anon', 'public.start_workspace_thread_generation(uuid,uuid,uuid,uuid,uuid,uuid,text)', 'EXECUTE') AND NOT has_function_privilege('anon', 'public.complete_workspace_thread_generation(uuid,uuid,uuid,uuid,text,text)', 'EXECUTE') diff --git a/supabase/tests/workspace_review_test.sql b/supabase/tests/workspace_review_test.sql index bb49f8707..02725fe26 100644 --- a/supabase/tests/workspace_review_test.sql +++ b/supabase/tests/workspace_review_test.sql @@ -1,14 +1,5 @@ BEGIN; -SELECT plan(25); - -SELECT has_table('public', 'workspace_proposals', 'proposal metadata is persisted'); -SELECT has_table('public', 'workspace_comments', 'inline comments are persisted'); -SELECT has_table('public', 'workspace_comment_replies', 'comment replies are persisted'); -SELECT has_table('public', 'workspace_notifications', 'mention notifications are persisted'); -SELECT has_function('public', 'create_workspace_proposal', ARRAY['uuid', 'uuid', 'uuid', 'bigint', 'bigint', 'jsonb'], 'proposal creation is authorized'); -SELECT has_function('public', 'create_workspace_comment', ARRAY['uuid', 'uuid', 'uuid', 'jsonb', 'text', 'uuid[]'], 'comment creation is authorized'); -SELECT has_function('public', 'reply_workspace_comment', ARRAY['uuid', 'uuid', 'uuid', 'text'], 'comment replies are authorized'); -SELECT has_function('public', 'resolve_workspace_comment', ARRAY['uuid', 'uuid', 'boolean'], 'comment resolution is authorized'); +SELECT plan(17); INSERT INTO auth.users (id) VALUES ('81111111-1111-4111-8111-111111111111'), diff --git a/supabase/tests/workspaces_rls_test.sql b/supabase/tests/workspaces_rls_test.sql index 37704003b..4d4f01e63 100644 --- a/supabase/tests/workspaces_rls_test.sql +++ b/supabase/tests/workspaces_rls_test.sql @@ -1,5 +1,5 @@ BEGIN; -SELECT plan(36); +SELECT plan(32); INSERT INTO auth.users (id) VALUES @@ -8,11 +8,6 @@ VALUES ('33333333-3333-4333-8333-333333333333'), ('44444444-4444-4444-8444-444444444444'); -SELECT has_table('public', 'workspaces', 'workspaces exists'); -SELECT has_table('public', 'workspace_members', 'workspace_members exists'); -SELECT has_table('public', 'workspace_documents', 'workspace_documents exists'); -SELECT has_table('public', 'workspace_audit_events', 'workspace_audit_events exists'); - SELECT results_eq( $$SELECT relrowsecurity FROM pg_class From 9eac01295236fc50dd874338c4158ed0fc2cd768 Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 2 Oct 2026 20:43:35 +0000 Subject: [PATCH 2/9] test(edge): cut Deno tests that restate prompts or duplicate others - Delete prompt tests that only assert fixed prompt wording, and control-matrix spot checks that repeat the matrix tests. - Delete tests that grep handler source text instead of calling it, and a cancelled-stream billing test that ai-stream's aborted scenario already covers. - Rewrite the demo-code contract test to call the real create-demo-code handler: it checks the stored hash, that the stored row never reveals the code, and that admin-role codes are refused. - Rewrite the model-catalog test to check the rules behind the list (low effort everywhere, valid defaults) instead of the ten IDs. Deno tests: 341 to 315. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../_shared/auto-model-router.test.ts | 11 +- supabase/functions/_shared/cors.test.ts | 7 +- .../functions/_shared/model-catalog.test.ts | 30 +--- supabase/functions/_shared/prompts.test.ts | 113 ------------- .../_shared/prompts/prompt-examples.test.ts | 10 -- .../admin/demo-code-contract.test.ts | 65 ++++++-- supabase/functions/ai-stream/prompt.test.ts | 12 -- supabase/functions/ai/control-matrix.test.ts | 155 ------------------ .../functions/ai/handler-research.test.ts | 55 ------- supabase/functions/ai/prompt-builder.test.ts | 47 +----- 10 files changed, 58 insertions(+), 447 deletions(-) diff --git a/supabase/functions/_shared/auto-model-router.test.ts b/supabase/functions/_shared/auto-model-router.test.ts index 0a48b5747..b6b0f4b52 100644 --- a/supabase/functions/_shared/auto-model-router.test.ts +++ b/supabase/functions/_shared/auto-model-router.test.ts @@ -1,8 +1,4 @@ -import { - AUTO_FAST, - AUTO_PRO, - isAutoModelSelection, -} from "./model-catalog.ts"; +import { AUTO_FAST, AUTO_PRO } from "./model-catalog.ts"; import { resolveModelSelection, type AutoModelRouterJev, @@ -33,11 +29,6 @@ function jevReturning(result: JevDecisionResult, observed?: { calls: number; sta }; } -Deno.test("Auto Fast is a Basic selection and Auto Pro is Pro-only", () => { - if (!isAutoModelSelection(AUTO_FAST) || !isAutoModelSelection(AUTO_PRO)) throw new Error("auto selections missing"); - if (AUTO_FAST !== "auto-fast" || AUTO_PRO !== "auto-pro") throw new Error("auto IDs changed"); -}); - Deno.test("Auto Fast normally resolves to GLM for a Basic user", async () => { const resolved = await resolveModelSelection({ requestedModel: AUTO_FAST, diff --git a/supabase/functions/_shared/cors.test.ts b/supabase/functions/_shared/cors.test.ts index 56d7c33cc..606375c55 100644 --- a/supabase/functions/_shared/cors.test.ts +++ b/supabase/functions/_shared/cors.test.ts @@ -1,4 +1,4 @@ -import { assertEquals, assertStringIncludes } from "jsr:@std/assert@1.0.19"; +import { assertEquals } from "jsr:@std/assert@1.0.19"; import { ALLOWED_ORIGINS, getCorsHeaders, isAllowedOrigin, isDevOrigin } from "./cors.ts"; Deno.test("allows the Capacitor WebView origin", () => { @@ -46,11 +46,6 @@ Deno.test("carries the requested methods", () => { ); }); -Deno.test("native share links use the public HTTPS origin", async () => { - const source = await Deno.readTextFile(new URL("../share/index.ts", import.meta.url)); - assertStringIncludes(source, "shareUrl: `${PUBLIC_WEB_APP_ORIGIN}/share/${data.share_code}`"); -}); - Deno.test("allows both Vercel projects' preview and staging hosts", () => { for (const origin of [ "https://debate-navigator-git-feat-accou-6d6edb-adam-gaborjanis-projects.vercel.app", diff --git a/supabase/functions/_shared/model-catalog.test.ts b/supabase/functions/_shared/model-catalog.test.ts index 6c81af147..f18ab5ea5 100644 --- a/supabase/functions/_shared/model-catalog.test.ts +++ b/supabase/functions/_shared/model-catalog.test.ts @@ -7,30 +7,14 @@ import { MODEL_CATALOG, } from "./model-catalog.ts"; -const APPROVED_IDS = [ - "openai/gpt-5.6-luna", - "google/gemini-3.8-flash", - "deepseek/deepseek-v4-flash-0731", - "anthropic/claude-sonnet-5", - "anthropic/claude-opus-5", - "openai/gpt-5.6-sol", - "openai/gpt-5.6-terra", - "deepseek/deepseek-v4-pro-0813", - "z-ai/glm-5.3-flash", - "meta/muse-spark-1.3", -] as const; - -Deno.test("model catalog contains the ten approved exact OpenRouter IDs", () => { - const ids = MODEL_CATALOG.map((entry) => entry.id); - if (ids.length !== APPROVED_IDS.length) throw new Error(`unexpected catalog size: ${ids.length}`); - for (const id of APPROVED_IDS) { - const entry = getModelCatalogEntry(id); - if (!entry || !ids.includes(id)) throw new Error(`missing approved model: ${id}`); - if (!entry.name || entry.maxTokens <= 0) throw new Error(`incomplete model entry: ${id}`); - if (!entry.supportedEfforts.includes("low")) throw new Error(`low effort is not declared for ${id}`); +Deno.test("every catalog model supports the forced trial effort and both defaults resolve to valid tiers", () => { + for (const entry of MODEL_CATALOG) { + if (getModelCatalogEntry(entry.id) !== entry) throw new Error(`catalog lookup lost ${entry.id}`); + if (!entry.supportedEfforts.includes("low")) throw new Error(`low effort is not declared for ${entry.id}`); } - if (DEFAULT_MODEL !== "openai/gpt-5.6-luna") throw new Error(`unexpected Basic default: ${DEFAULT_MODEL}`); - if (DEFAULT_PRO_MODEL !== "anthropic/claude-sonnet-5") throw new Error(`unexpected Pro default: ${DEFAULT_PRO_MODEL}`); + const basic = getModelCatalogEntry(DEFAULT_MODEL); + if (!basic || basic.isProOnly) throw new Error(`Basic default is missing or Pro-only: ${DEFAULT_MODEL}`); + if (!getModelCatalogEntry(DEFAULT_PRO_MODEL)) throw new Error(`Pro default is not in the catalog: ${DEFAULT_PRO_MODEL}`); }); Deno.test("model catalog marks only the approved Pro entries as Pro-only", () => { diff --git a/supabase/functions/_shared/prompts.test.ts b/supabase/functions/_shared/prompts.test.ts index b778ba4ac..e747b9e36 100644 --- a/supabase/functions/_shared/prompts.test.ts +++ b/supabase/functions/_shared/prompts.test.ts @@ -6,7 +6,6 @@ import { FACTOIDS_PROMPT, GENERATE_PROMPT, JSON_RULES, - PRODUCT_IDENTITY, RESEARCH_RULES, SPARRING_PROMPT, boundPromptSources, @@ -88,19 +87,6 @@ Deno.test("research citation rules appear exactly when research is attached", () } }); -Deno.test("research and citation honesty rules are present when research is provided", () => { - const prompt = buildSystemPrompt({ feature: "ask", researchContext: "=== WEB RESEARCH RESULTS ===" }); - if (!prompt.includes(RESEARCH_RULES.slice(0, 60))) throw new Error("research rules missing"); - if (!prompt.includes("=== WEB RESEARCH RESULTS ===")) throw new Error("research context missing"); -}); - -Deno.test("research rules are absent when no research was provided", () => { - const prompt = buildSystemPrompt({ feature: "brainstorm" }); - if (prompt.includes("RESEARCH CITATION RULES:") || prompt.includes(RESEARCH_RULES.slice(0, 60))) { - throw new Error("citation rules leaked into a prompt without research"); - } -}); - Deno.test("admin, library, debate, and student-material rules follow the task layer in order", () => { const prompt = buildSystemPrompt({ feature: "generate", @@ -126,12 +112,6 @@ Deno.test("admin, library, debate, and student-material rules follow the task la if (!prompt.endsWith(UNTRUSTED_CONTENT_RULES)) throw new Error("student-material rules must close the prompt"); }); -Deno.test("the retired per-user custom prompt has no prompt layer", () => { - const prompt = buildSystemPrompt({ feature: "ask", globalPrompt: "ADMIN RULE" }); - if (prompt.includes("CUSTOM SYSTEM PROMPT")) throw new Error("custom prompt layer still rendered"); - if (prompt.includes("STUDENT MATERIAL RULES")) throw new Error("material rules added without student material"); -}); - Deno.test("date context replaces the placeholder in every feature task prompt", () => { for (const feature of FEATURES) { const prompt = buildSystemPrompt({ feature, dateContext: "CURRENT DATE: Testdate" }); @@ -140,99 +120,6 @@ Deno.test("date context replaces the placeholder in every feature task prompt", } }); -Deno.test("product identity includes the humanizer style rules", () => { - if (!PRODUCT_IDENTITY.includes("HUMANIZER STYLE INSTRUCTIONS")) throw new Error("humanizer missing from identity layer"); -}); - -Deno.test("feature prompts carry plain-language calibration", () => { - const expectedByFeature: Record = { - generate: "Match the requested audience, length, and tone.", - attack: "Be direct and substantive, not hostile.", - brainstorm: "Use plain language and concrete examples.", - factoids: "Write evidence in clear, card-ready language.", - ask: "Answer the user's question first in plain language.", - sparring: "Difficulty changes argument pressure, not vocabulary.", - crossfire: "Ask one direct question at a time.", - }; - - for (const feature of FEATURES) { - const prompt = buildSystemPrompt({ feature }); - if (!prompt.includes(expectedByFeature[feature])) { - throw new Error(`${feature}: plain-language calibration missing`); - } - } -}); - -Deno.test("Crossfire keeps replies short and easy to say aloud", () => { - const prompt = buildSystemPrompt({ feature: "crossfire" }); - if (!prompt.includes("Keep the answer short and easy to say aloud.")) { - throw new Error("Crossfire speech-length guidance missing"); - } - if (!prompt.includes("Questions should be one sentence. Answers should be no more than three short sentences.")) { - throw new Error("Crossfire sentence-length guidance missing"); - } - if (!prompt.includes("Use the user's own words where possible")) { - throw new Error("Crossfire vocabulary guidance missing"); - } - if (!prompt.includes("Tie each reply to the latest relevant transcript turn")) { - throw new Error("Crossfire transcript-targeting guidance missing"); - } -}); - -Deno.test("shared language rules honor explicit output-language instructions", () => { - const prompt = buildSystemPrompt({ feature: "generate" }); - if (!prompt.includes("Follow any explicit output-language instruction. Otherwise match the language used by the user.")) { - throw new Error("explicit output-language rule missing"); - } -}); - -Deno.test("sparring system prompt defines difficulty and transcript-only scoring", () => { - const prompt = buildSystemPrompt({ feature: "sparring", dateContext: "CURRENT DATE: Testdate" }); - if (!prompt.includes("Difficulty is a real control")) throw new Error("difficulty guidance missing"); - if (!prompt.includes("clarity, organization, and time discipline")) throw new Error("typed delivery guidance missing"); - if (!prompt.includes("Never invent citations")) throw new Error("transcript-only guidance missing"); -}); - -Deno.test("sparring system prompt teaches the school 4-6-6-3 speaker duties", () => { - const prompt = buildSystemPrompt({ feature: "sparring", dateContext: "CURRENT DATE: August 31, 2026 (2026)" }); - for (const expected of ["Introductor", "R1", "R2", "Conclusor", "no new arguments"]) { - if (!prompt.includes(expected)) throw new Error(`school role guidance missing: ${expected}`); - } -}); - -Deno.test("sparring system prompt keeps LD and PF duties separate from school roles", () => { - const prompt = buildSystemPrompt({ feature: "sparring", dateContext: "CURRENT DATE: August 31, 2026 (2026)" }); - if (!prompt.includes('When format is "standard"')) throw new Error("school role condition missing"); - if (!prompt.includes('When format is "ld" or "pf"')) throw new Error("LD/PF role condition missing"); - if (!prompt.includes("rather than importing the school roles")) throw new Error("format separation missing"); -}); - -Deno.test("sparring system prompt names the behavioral rules for school, LD, and PF", () => { - const prompt = buildSystemPrompt({ feature: "sparring" }); - for (const expected of [ - 'When format is "standard"', - 'When format is "ld" or "pf"', - "Difficulty is a real control", - "Follow the requested language for every prose field", - ]) { - if (!prompt.includes(expected)) throw new Error(`sparring control guidance missing: ${expected}`); - } - if (prompt.includes("RESEARCH CITATION RULES:")) throw new Error("sparring received research rules without research"); -}); - -Deno.test("crossfire system prompt carries strict turn and debrief safety rules", () => { - const prompt = buildSystemPrompt({ feature: "crossfire" }); - if (!prompt.includes("Each non-empty debrief item must cite only actual turn IDs")) { - throw new Error("crossfire turn-reference rule missing"); - } - if (!prompt.includes("without insults, threats, harassment")) { - throw new Error("crossfire style safety rule missing"); - } - if (prompt.includes("RESEARCH CITATION RULES:")) { - throw new Error("crossfire unexpectedly carries live research rules"); - } -}); - Deno.test("prompt source library is capped by count, per-source text, and aggregate text", () => { const sources = Array.from({ length: MAX_PROMPT_SOURCE_COUNT + 2 }, (_, index) => ({ title: `Source ${index}`, diff --git a/supabase/functions/_shared/prompts/prompt-examples.test.ts b/supabase/functions/_shared/prompts/prompt-examples.test.ts index 7fa9d2b24..b41e4ac46 100644 --- a/supabase/functions/_shared/prompts/prompt-examples.test.ts +++ b/supabase/functions/_shared/prompts/prompt-examples.test.ts @@ -50,16 +50,6 @@ function promptsFor(example: typeof PROMPT_EXAMPLES[number]) { return { system, user, score }; } -Deno.test("every feature has two representative examples", () => { - for (const feature of Object.keys(QUALITY_RULES) as Feature[]) { - const examples = PROMPT_EXAMPLES.filter((example) => example.feature === feature); - assert(examples.length === 2, `${feature} needs exactly two examples`); - if (feature !== "ask" && feature !== "crossfire") { - assert(new Set(examples.map((example) => example.side)).size === 2, `${feature} examples must cover both sides`); - } - } -}); - for (const example of PROMPT_EXAMPLES) { Deno.test(`prompt example ${example.id} carries its quality rules and allowed debate material`, () => { const { system, user, score } = promptsFor(example); diff --git a/supabase/functions/admin/demo-code-contract.test.ts b/supabase/functions/admin/demo-code-contract.test.ts index 081cadff2..5c6a38241 100644 --- a/supabase/functions/admin/demo-code-contract.test.ts +++ b/supabase/functions/admin/demo-code-contract.test.ts @@ -1,26 +1,57 @@ -import { getDemoLoginRole, isCreatableDemoRole } from "../_shared/demo-code.ts"; +for (const [key, value] of Object.entries({ + SUPABASE_URL: "https://supabase.test", + SUPABASE_ANON_KEY: "test-anon-key", + SUPABASE_SERVICE_ROLE_KEY: "test-service-role-key", +})) Deno.env.set(key, value); -Deno.test("admin exposes the demo-code lifecycle contract", async () => { - const source = await Deno.readTextFile(new URL("./index.ts", import.meta.url)); - const loginSource = await Deno.readTextFile(new URL("../demo-code-login/index.ts", import.meta.url)); +const { handleRequest } = await import("./index.ts"); +const { getDemoLoginRole, hashDemoCode, isCreatableDemoRole } = await import("../_shared/demo-code.ts"); - for (const action of ["demo-codes", "create-demo-code", "disable-demo-code", "reset-demo-code"]) { - if (!source.includes(`action === "${action}"`)) { - throw new Error(`missing admin action: ${action}`); +async function createDemoCode(body: Record) { + const inserts: Array> = []; + const originalFetch = globalThis.fetch; + globalThis.fetch = (async (input: Request | URL | string, init?: RequestInit) => { + const request = input instanceof Request ? input : new Request(input, init); + const url = new URL(request.url); + if (url.pathname === "/auth/v1/user") return Response.json({ id: "admin-1", aud: "authenticated" }); + if (url.pathname === "/rest/v1/user_roles") return Response.json({ role: "admin" }); + if (url.pathname === "/rest/v1/demo_access_codes" && request.method === "POST") { + const row = await request.json() as Record; + inserts.push(row); + return Response.json({ id: "code-1", label: row.label, email: row.email, role: row.role }, { status: 201 }); } + throw new Error(`Unexpected request: ${request.method} ${url}`); + }) as typeof fetch; + try { + const response = await handleRequest(new Request("http://127.0.0.1/functions/v1/admin?action=create-demo-code", { + method: "POST", + headers: { Authorization: "Bearer admin-token", "Content-Type": "application/json" }, + body: JSON.stringify(body), + })); + return { inserts, response, payload: await response.json() as { code?: string; error?: string } }; + } finally { + globalThis.fetch = originalFetch; } +} - if (!source.includes("codeHash") || !source.includes("generateDemoCode")) { - throw new Error("admin creation must hash and generate demo codes"); +Deno.test("create-demo-code returns the raw code once, stores only its hash, and refuses other roles", async () => { + const { inserts, response, payload } = await createDemoCode({ label: "Tournament room" }); + if (response.status !== 201) throw new Error(`create returned ${response.status}: ${JSON.stringify(payload)}`); + const code = payload.code ?? ""; + if (!/^DEMO-[A-Z0-9]{4}-[A-Z0-9]{4}$/.test(code)) throw new Error(`create did not return a usable code: ${code}`); + if (inserts.length !== 1) throw new Error(`expected one insert, got ${inserts.length}`); + const [row] = inserts; + if (row.code_hash !== await hashDemoCode(code)) throw new Error("the stored hash does not match the returned code"); + if (row.role !== "user") throw new Error(`demo code stored role ${String(row.role)}`); + const stored = JSON.stringify(row).toLowerCase(); + const secret = code.slice(5).replace("-", "").toLowerCase(); + if (stored.includes(code.toLowerCase()) || stored.includes(secret)) { + throw new Error("the raw code, or the assigned email, reveals the code in the stored row"); } - if (!source.includes("code: rawCode")) { - throw new Error("create must return the raw code in its one-time response"); - } - if (source.includes("rawCode.replaceAll")) { - throw new Error("the assigned email must not reveal or derive the raw code"); - } - if (!loginSource.includes("isDemoUserForCode") || !loginSource.includes("email_conflict")) { - throw new Error("demo login must not reuse an unrelated existing account"); + + const refused = await createDemoCode({ label: "Tournament room", role: "admin" }); + if (refused.response.status !== 400 || refused.inserts.length !== 0) { + throw new Error(`admin-role demo code returned ${refused.response.status} with ${refused.inserts.length} inserts`); } }); diff --git a/supabase/functions/ai-stream/prompt.test.ts b/supabase/functions/ai-stream/prompt.test.ts index c28e7ac52..7f008e2f1 100644 --- a/supabase/functions/ai-stream/prompt.test.ts +++ b/supabase/functions/ai-stream/prompt.test.ts @@ -1,4 +1,3 @@ -import { isAskStreamingModeAllowed } from "../_shared/control-enforcement.ts"; import { buildStreamingAskSystemPrompt, PRODUCT_IDENTITY, @@ -55,14 +54,3 @@ Deno.test("streaming Ask keeps the shared identity, language, and evidence rules "OUTPUT RULES: Return ONLY a valid JSON object", ); }); - -Deno.test("streaming Ask still allows only general mode", () => { - if (!isAskStreamingModeAllowed("general")) { - throw new Error("general Ask mode was rejected"); - } - for (const mode of ["clarify", "summarize", "proscons", "argumentmap"]) { - if (isAskStreamingModeAllowed(mode)) { - throw new Error(`${mode} Ask mode was allowed to stream`); - } - } -}); diff --git a/supabase/functions/ai/control-matrix.test.ts b/supabase/functions/ai/control-matrix.test.ts index 70b3c59a3..db941a48e 100644 --- a/supabase/functions/ai/control-matrix.test.ts +++ b/supabase/functions/ai/control-matrix.test.ts @@ -10,7 +10,6 @@ import { enforceAttackControls, enforceBrainstormControls, enforceGenerateControls, - isAskStreamingModeAllowed, } from "../_shared/control-enforcement.ts"; import { buildSearchRequests } from "../_shared/source-quality.ts"; @@ -22,38 +21,6 @@ function assert(condition: boolean, label: string) { if (!condition) throw new Error(`[${label}] behavioral effect missing`); } -Deno.test("generate controls propagate to prompt and post-processing", () => { - const genBrief = buildUserPrompt("generate", { motion: "M", side: "pro", length: "brief", role: "r1" }, ""); - assertIncludes(genBrief, "Length: brief", "generate.length payload"); - assertIncludes(genBrief, "Length Instruction: Return exactly 2 contentions", "generate.length prompt"); - const out: Record = { contentions: [{}, {}, {}], metadata: {} }; - enforceGenerateControls(out, "pro", "custom", "brief", "intro"); - assert((out.contentions as unknown[]).length === 2, "generate.length post-processing"); - - const underlength: Record = { contentions: [{}], metadata: {} }; - let rejected = false; - try { - enforceGenerateControls(underlength, "pro", "custom", "comprehensive", "r1"); - } catch (error) { - rejected = error instanceof Error && error.message.includes("requires exactly 4"); - } - assert(rejected, "generate.length underlength responses fail closed"); - assert((underlength.contentions as unknown[]).length === 1, "generate.length never fabricates contentions"); - - assertIncludes( - buildUserPrompt("generate", { motion: "M", side: "pro", audience: "advanced", role: "r1" }, ""), - "Audience Instruction: Assume an advanced debater", - "generate.audience", - ); - assertIncludes( - buildUserPrompt("generate", { motion: "M", side: "pro", tone: "aggressive", role: "r1" }, ""), - "Tone Instruction: Use sharper strategic phrasing", - "generate.tone", - ); - assertIncludes(buildUserPrompt("generate", { motion: "M", side: "con", role: "r1" }, ""), "Side: con", "generate.side"); - assertIncludes(buildUserPrompt("generate", { motion: "M", side: "pro", role: "conc" }, ""), "Role: conc", "generate.role"); -}); - Deno.test("generate length and role controls preserve the output contract", () => { const targets = { brief: 2, standard: 3, comprehensive: 4 } as const; for (const length of ["brief", "standard", "comprehensive"] as const) { @@ -70,29 +37,6 @@ Deno.test("generate length and role controls preserve the output contract", () = } }); -Deno.test("attack controls propagate to prompt and post-processing", () => { - const attackOff = buildUserPrompt("attack", { caseText: "C", detectFallacies: false, analyzeWeighing: false }, ""); - assertIncludes(attackOff, "Detect Fallacies: disabled", "attack.fallacies payload"); - assertIncludes(attackOff, "Analyze Weighing: disabled", "attack.weighing payload"); - const out: Record = { - fallaciesDetected: [{}], - weighingAnalysis: { currentWeighing: "x", missingComparisons: ["y"], improvementSuggestions: ["z"] }, - }; - enforceAttackControls(out, false, false); - assert((out.fallaciesDetected as unknown[]).length === 0, "attack.fallacies post-processing"); - - assertIncludes( - buildUserPrompt("attack", { caseText: "C", aggressiveness: "ruthless", judgeParadigm: "policy" }, ""), - "Attack Intensity: ruthless", - "attack.intensity", - ); - assertIncludes( - buildUserPrompt("attack", { caseText: "C", judgeParadigm: "values" }, ""), - "Judge Paradigm: values", - "attack.paradigm", - ); -}); - Deno.test("attack toggle controls retain or clear only their requested sections", () => { for (const detectFallacies of [true, false]) { for (const analyzeWeighing of [true, false]) { @@ -119,18 +63,6 @@ Deno.test("attack toggle controls retain or clear only their requested sections" } }); -Deno.test("brainstorm controls propagate to prompt and post-processing", () => { - assertIncludes( - buildUserPrompt("brainstorm", { motion: "M", side: "pro", outputMode: "buckets" }, ""), - "Output Mode: buckets", - "brainstorm.outputMode payload", - ); - const out: Record = { topFive: [], mindmap: [], buckets: [] }; - enforceBrainstormControls(out, "pro", "buckets"); - assert("buckets" in out && !("topFive" in out), "brainstorm.outputMode post-processing"); - assertIncludes(buildUserPrompt("brainstorm", { motion: "M", side: "both", outputMode: "topfive" }, ""), "Side: both", "brainstorm.side"); -}); - Deno.test("brainstorm output mode controls keep only the requested branch", () => { for (const side of ["pro", "con", "both"] as const) { for (const outputMode of ["topfive", "mindmap", "buckets"] as const) { @@ -145,23 +77,6 @@ Deno.test("brainstorm output mode controls keep only the requested branch", () = } }); -Deno.test("factoids controls propagate to prompt and query construction", () => { - assertIncludes( - buildUserPrompt("factoids", { claim: "C", region: "EU", sourceType: "government", timeframe: "2024-2026" }, ""), - "Source Type: government", - "factoids.sourceType prompt", - ); - assertIncludes( - buildUserPrompt("factoids", { claim: "C", region: "EU", timeframe: "2024-2026" }, ""), - "Timeframe: 2024-2026", - "factoids.timeframe prompt", - ); - const govRequests = buildSearchRequests("C", "EU", "government", "2024-2026"); - assert(govRequests.some((request) => request.includeDomains?.includes("energy.gov")), "factoids.sourceType retrieval"); - assert(govRequests.some((request) => request.startPublishedDate?.startsWith("2024")), "factoids.timeframe retrieval"); - assert(govRequests.some((request) => request.endPublishedDate?.startsWith("2026")), "factoids.timeframe upper bound"); -}); - Deno.test("factoids matrix preserves legal, statistics, academic, recent, direction, and count effects", () => { const legalRequests = buildSearchRequests("Is this law constitutional?", "EU", "government", "2024-2026"); assert(legalRequests.some((request) => request.query.includes("official law court ruling")), "factoids.legal query"); @@ -190,55 +105,6 @@ Deno.test("factoids matrix preserves legal, statistics, academic, recent, direct assertIncludes(againstPrompt, "Source Count: 12", "factoids.sourceCount prompt"); }); -Deno.test("ask toggles and modes appear verbatim in the request prompt", () => { - assertIncludes(buildUserPrompt("ask", { message: "Hi", mode: "clarify" }, ""), "Mode Guidance:", "ask.mode payload"); - - const allOn = buildAskBasePrompt({ - message: "Hi", - mode: "clarify", - citationMode: true, - webSearchEnabled: true, - prosConsMode: true, - argumentMapMode: true, - researchAvailable: true, - }); - assertIncludes(allOn, "Citation Mode: ON", "ask.citationMode payload"); - assertIncludes(allOn, "Web Search: ENABLED", "ask.webSearch payload"); - assertIncludes(allOn, "Pros/Cons: ON", "ask.prosCons payload"); - assertIncludes(allOn, "Argument Map: ON", "ask.argumentMap payload"); - assertIncludes(allOn, "Research Context: AVAILABLE", "ask.research gating"); - - const allOff = buildAskBasePrompt({ - message: "Hi", - mode: "general", - citationMode: false, - webSearchEnabled: false, - prosConsMode: false, - argumentMapMode: false, - researchAvailable: false, - }); - assertIncludes(allOff, "Citation Mode: OFF", "ask.citationMode off state"); - assertIncludes(allOff, "Web Search: DISABLED", "ask.webSearch off state"); - assertIncludes(allOff, "Pros/Cons: OFF", "ask.prosCons off state"); - assertIncludes(allOff, "Argument Map: OFF", "ask.argumentMap off state"); - assertIncludes(allOff, "Research Context: NONE", "ask.research off state"); - - const out: Record = { - response: "r", - followUpQuestions: ["q"], - uncertainties: [] as string[], - prosConsAnalysis: { pros: ["p"], cons: ["c"] }, - argumentMap: { mainClaim: "m", supportingPoints: [], counterpoints: [], assumptions: [] }, - }; - enforceAskControls(out, { mode: "clarify", citationMode: true, prosConsMode: true, argumentMapMode: true }); - assert((out.uncertainties as string[]).length === 0, "ask.clarify quiet when follow-ups exist"); - assert("prosConsAnalysis" in out && "argumentMap" in out, "ask toggles on keep sections"); - - const stripped: Record = { response: "r", followUpQuestions: [], uncertainties: [], citations: [{}] }; - enforceAskControls(stripped, { mode: "general" }); - assert(Array.isArray(stripped.citations) && stripped.citations.length === 0, "ask citations off clears list"); -}); - Deno.test("ask matrix preserves every mode, toggle, research, and output effect", () => { const commonLanguageRule = "Follow any explicit output-language instruction. Otherwise match the language used by the user."; @@ -315,24 +181,3 @@ Deno.test("ask matrix preserves every mode, toggle, research, and output effect" } } }); - -Deno.test("prompt matrix applies the explicit Hungarian output contract", () => { - const instruction = "Language: Hungarian (hu) - Write ALL output in this language."; - for (const [feature, input] of [ - ["generate", { motion: "M", language: "hu" }], - ["attack", { caseText: "C", language: "hu" }], - ["brainstorm", { motion: "M", language: "hu" }], - ["factoids", { claim: "C", language: "hu" }], - ["ask", { message: "Q", language: "hu" }], - ["sparring", { motion: "M", language: "hu" }], - ] as const) { - assertIncludes(buildUserPrompt(feature, input, ""), instruction, `${feature}.language`); - } -}); - -Deno.test("only plain general ask mode is eligible for streaming", () => { - for (const mode of ["clarify", "summarize", "proscons", "argumentmap"]) { - assert(!isAskStreamingModeAllowed(mode), `ask.${mode} streaming bypass`); - } - assert(isAskStreamingModeAllowed("general"), "ask.general streaming eligibility"); -}); diff --git a/supabase/functions/ai/handler-research.test.ts b/supabase/functions/ai/handler-research.test.ts index 97110a756..7732ba838 100644 --- a/supabase/functions/ai/handler-research.test.ts +++ b/supabase/functions/ai/handler-research.test.ts @@ -856,61 +856,6 @@ Deno.test("streaming rejects provider errors and incomplete EOFs, while acceptin } }); -Deno.test("cancelled streams keep observed cost unknown", async () => { - const originalFetch = globalThis.fetch; - const usageRows: Array> = []; - const encoder = new TextEncoder(); - let providerStartedResolve!: () => void; - const providerStarted = new Promise((resolve) => providerStartedResolve = resolve); - globalThis.fetch = (async (input: Request | URL | string, init?: RequestInit) => { - const url = new URL(input instanceof Request ? input.url : String(input)); - const supabaseResponse = supabaseTestResponse(url); - if (supabaseResponse) { - if (url.pathname.includes("/rest/v1/ai_requests")) usageRows.push(JSON.parse(String(init?.body)) as Record); - return supabaseResponse; - } - if (url.href === "https://openrouter.ai/api/v1/chat/completions") { - const body = new ReadableStream({ - start(controller) { - controller.enqueue(encoder.encode(`data: ${JSON.stringify({ choices: [{ delta: { content: "partial" } }] })}\n\n`)); - controller.enqueue(encoder.encode(`data: ${JSON.stringify({ choices: [], usage: { prompt_tokens: 2, completion_tokens: 1, cost: 0.25 } })}\n\n`)); - providerStartedResolve(); - init?.signal?.addEventListener("abort", () => controller.error(new DOMException("Upstream aborted", "AbortError")), { once: true }); - }, - pull() { - return new Promise(() => {}); - }, - }); - return new Response(body, { headers: { "Content-Type": "text/event-stream" } }); - } - throw new Error(`Unexpected request: ${url}`); - }) as typeof fetch; - - const testState = globalThis as typeof globalThis & { __testTier?: string }; - const originalTier = testState.__testTier; - testState.__testTier = "trial"; - const caller = new AbortController(); - try { - const response = await handleStreamRequest(new Request("http://127.0.0.1/functions/v1/ai-stream", { - method: "POST", - headers: { Authorization: "Bearer test-user-token", "Content-Type": "application/json" }, - body: JSON.stringify({ feature: "ask", model: "openai/gpt-5.6-luna", input: { message: "Hello", mode: "general" } }), - signal: caller.signal, - })); - const read = response.text(); - await providerStarted; - await new Promise((resolve) => setTimeout(resolve, 5)); - caller.abort(); - await read; - const row = usageRows.at(-1); - if (row?.status !== "cancelled" || row.cost_usd !== null) throw new Error(`cancelled stream was billed: ${JSON.stringify(row)}`); - } finally { - globalThis.fetch = originalFetch; - if (originalTier === undefined) delete testState.__testTier; - else testState.__testTier = originalTier; - } -}); - Deno.test("cancelled JSON repair keeps observed tokens but makes the total cost unknown", async () => { const originalFetch = globalThis.fetch; const usageRows: Array> = []; diff --git a/supabase/functions/ai/prompt-builder.test.ts b/supabase/functions/ai/prompt-builder.test.ts index c9c6847d2..04cf305a3 100644 --- a/supabase/functions/ai/prompt-builder.test.ts +++ b/supabase/functions/ai/prompt-builder.test.ts @@ -113,6 +113,7 @@ Deno.test("attack prompt includes intensity, paradigm, and toggle states", () => Deno.test("every AI feature carries an explicit Hungarian output instruction", () => { const instruction = "Language: Hungarian (hu) - Write ALL output in this language."; const prompts = [ + buildUserPrompt("generate", { motion: "MOTION", language: "hu" }, ""), buildUserPrompt("attack", { caseText: "CASE", language: "hu" }, ""), buildUserPrompt("brainstorm", { motion: "MOTION", language: "hu" }, ""), buildUserPrompt("factoids", { claim: "CLAIM", language: "hu" }, ""), @@ -197,22 +198,6 @@ Deno.test("brainstorm prompt matrix preserves both one-side and comparative inpu } }); -Deno.test("factoids prompt includes source filters and language", () => { - const prompt = buildUserPrompt("factoids", { - claim: "Solar is cheaper than coal", - region: "United States", - sourceType: "government", - timeframe: "2020-2025", - language: "en", - }, "SEARCH CONTEXT"); - - assertIncludes(prompt, "Find evidence in favor of this statement: \"Solar is cheaper than coal\""); - assertIncludes(prompt, "Region: United States"); - assertIncludes(prompt, "Source Type: government"); - assertIncludes(prompt, "Timeframe: 2020-2025"); - assertIncludes(prompt, "Language: English (en)"); -}); - Deno.test("factoids prompt matrix preserves legal, statistics, academic, recent, direction, and count inputs", () => { const fixtures = [ { @@ -375,36 +360,6 @@ Deno.test("sparring score prompt has no current turn", () => { assertIncludes(prompt, "Current Turn: none for scoring"); }); -Deno.test("crossfire next-turn prompt carries the expected reply and real turn IDs", () => { - const prompt = buildCrossfireNextTurnPrompt({ - config: { - resolution: "Cities should ban private cars downtown.", - argument: "Private cars create congestion and unsafe streets.", - mode: "question-ai", - style: "aggressive", - durationSeconds: 300, - useSourceLibrary: false, - seedQuestions: ["What is the mechanism?"], - }, - transcript: [{ - id: "T1", - sequence: 1, - speaker: "user", - kind: "question", - text: "What is the mechanism?", - createdAt: 1, - }], - expectedReply: "answer", - language: "hu", - }); - - assertIncludes(prompt, "The next model reply must be type \"answer\""); - assertIncludes(prompt, "turn-id=T1"); - assertIncludes(prompt, "aggressive"); - assertIncludes(prompt, "Do not insult, threaten, harass"); - assertIncludes(prompt, "Language: Hungarian (hu) - Write ALL output in this language."); -}); - Deno.test("crossfire next-turn builder preserves all three style controls", () => { const styleGuidance = { cooperative: "Do not force a concession", From 8692a3dd0ab5c9efa7f502dab2325894dd8634a2 Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 2 Oct 2026 20:43:35 +0000 Subject: [PATCH 3/9] test(collab): assert the membership query arguments The isAuthorized test only checked the fake database's canned `true`, so swapped document and author arguments would still pass. It now asserts the exact parameters sent to the membership query. Co-Authored-By: Claude Opus 5.5 (1M context) --- services/collaboration/src/postgres-store.test.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/services/collaboration/src/postgres-store.test.ts b/services/collaboration/src/postgres-store.test.ts index e98af150c..44919fc78 100644 --- a/services/collaboration/src/postgres-store.test.ts +++ b/services/collaboration/src/postgres-store.test.ts @@ -224,6 +224,15 @@ describe("Postgres collaboration writer locks", () => { documentId: "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", epoch: 3, })).resolves.toBe(true); + expect(client.query).toHaveBeenCalledWith( + expect.stringContaining("member.user_id = $3"), + [ + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + 3, + "cccccccc-cccc-4ccc-8ccc-cccccccccccc", + false, + ], + ); expect(client.release).toHaveBeenCalledTimes(1); }); From c921e4eb2fb639bebf1ee4569367b21d3b3d0e1c Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 3 Oct 2026 06:45:25 +0000 Subject: [PATCH 4/9] test(misc): drop Vercel config cases that restate literals Drop four vercel-deployment-config cases that restate config literals, static copy or dependency keys. The security-header, SPA-fallback and cache-rule cases stay. Co-Authored-By: Claude Opus 5.5 (1M context) --- src/test/vercel-deployment-config.test.ts | 52 +---------------------- 1 file changed, 1 insertion(+), 51 deletions(-) diff --git a/src/test/vercel-deployment-config.test.ts b/src/test/vercel-deployment-config.test.ts index a7da83d1c..2da68064f 100644 --- a/src/test/vercel-deployment-config.test.ts +++ b/src/test/vercel-deployment-config.test.ts @@ -8,10 +8,6 @@ type HeaderRule = { }; type VercelConfig = { - $schema?: string; - framework?: string; - buildCommand?: string; - outputDirectory?: string; rewrites?: Array<{ source: string; destination: string }>; headers?: HeaderRule[]; }; @@ -21,18 +17,10 @@ function loadConfig(): VercelConfig { return JSON.parse(readFileSync(path, "utf8")) as VercelConfig; } -function loadPackageJson(): { - engines?: { node?: string }; - dependencies?: Record; - scripts?: Record; -} { +function loadPackageJson(): { scripts?: Record } { return JSON.parse(readFileSync(resolve(process.cwd(), "package.json"), "utf8")); } -function loadMainSource(): string { - return readFileSync(resolve(process.cwd(), "src/main.tsx"), "utf8"); -} - function readProjectFile(path: string): string { return readFileSync(resolve(process.cwd(), path), "utf8"); } @@ -44,15 +32,6 @@ function headersFor(config: VercelConfig, source: string) { } describe("Vercel frontend deployment configuration", () => { - it("builds DebateLab as a Vite static site", () => { - const config = loadConfig(); - - expect(config.$schema).toBe("https://openapi.vercel.sh/vercel.json"); - expect(config.framework).toBe("vite"); - expect(config.buildCommand).toBe("npm run build"); - expect(config.outputDirectory).toBe("dist"); - }); - it("falls back to the SPA shell after Vercel checks the filesystem", () => { const config = loadConfig(); @@ -119,17 +98,6 @@ describe("Vercel frontend deployment configuration", () => { expect(indexHtml).toContain(''); }); - it("starts with a branded boot shell instead of visible crawl-only copy", () => { - const indexHtml = readProjectFile("index.html"); - const mainSource = loadMainSource(); - const rootMarkup = indexHtml.match(/
([\s\S]*?)<\/div>/)?.[1] ?? ""; - - expect(indexHtml).toContain('aria-label="DebateLab is loading"'); - expect(rootMarkup).not.toContain("DebateLab by Programcode is the official"); - expect(indexHtml).toMatch(/