import { env } from "@code/env/convex"; import { type TestConvex, convexTest } from "convex-test"; import { anyApi } from "convex/server"; import { describe, expect, test } from "vitest"; import { internal } from "./_generated/api"; import type { Id } from "./_generated/dataModel"; import schema from "./schema"; declare global { interface ImportMeta { readonly glob: (pattern: string) => Record Promise>; } } const modules = import.meta.glob("./**/*.ts"); const api = anyApi; const TOKEN = env.FLUE_DB_TOKEN; const BAD_TOKEN = "not-the-right-token"; const ID_A = "https://convex.test|user-a"; const identityA = { tokenIdentifier: ID_A }; const newTest = () => convexTest({ schema, modules }); const ensureOrg = async (t: TestConvex) => { const org = await t .withIdentity(identityA) .mutation(api.organizations.ensurePersonalOrganization, {}); return org._id as Id<"organizations">; }; const problemStatement = { title: "Deploy fails on staging", summary: "The staging deploy command exits with a DNS resolution error.", desiredOutcome: "Staging deploys successfully without DNS errors.", constraints: ["Must not change the production pipeline"], }; const seedMessage = async ( t: TestConvex, orgId: Id<"organizations">, clientRequestId: string, rawText: string ): Promise => { const begun = await t .withIdentity(identityA) .mutation(api.conversationMessages.beginUserMessage, { clientRequestId, organizationId: orgId, rawText, }); await t .withIdentity(identityA) .mutation(api.conversationMessages.markAdmitted, { clientRequestId, organizationId: orgId, submissionId: `sub-${clientRequestId}`, }); return begun.messageId; }; const createProject = async ( t: TestConvex, ownerId: string, repoName: string ): Promise> => { const ownerIdentity = { tokenIdentifier: ownerId }; await t .withIdentity(ownerIdentity) .mutation(api.organizations.ensurePersonalOrganization); const outcome = await t .withIdentity(ownerIdentity) .mutation(internal.projects.persistPublicGitImport, { userId: ownerId, source: { host: "github.com", projectName: repoName, repositoryPath: `owner/${repoName}`, normalizedUrl: `https://github.com/owner/${repoName}`, url: `https://github.com/owner/${repoName}`, }, remote: { defaultBranch: "main", documents: [ { kind: "readme" as const, path: "README.md", content: `# ${repoName}\n\nTest.\n`, }, ], warnings: [], }, }); return outcome.id as unknown as Id<"projects">; }; // Create a project-scoped signal via the routing backend. const createRoutingSignal = async ( t: TestConvex, orgId: Id<"organizations">, projectId: Id<"projects">, messageIds: string[] ): Promise> => { const result = await t.mutation(api.signalRouting.createSignal, { messageIds, organizationId: orgId, problemStatement, processedByAgentInstanceId: orgId, projectId, token: TOKEN, }); return result.signalId as Id<"signals">; }; describe("signalRouting authentication", () => { test("invalid token is rejected on every function", async () => { const t = newTest(); const orgId = await ensureOrg(t); const projectId = await createProject(t, ID_A, "auth-test"); const messageId = await seedMessage(t, orgId, "req-auth", "test message"); await expect( t.mutation(api.signalRouting.createSignal, { messageIds: [messageId], organizationId: orgId, problemStatement, processedByAgentInstanceId: orgId, projectId, token: BAD_TOKEN, }) ).rejects.toThrow(/Invalid agent control token/u); await expect( t.query(api.signalRouting.listEvidence, { organizationId: orgId, token: BAD_TOKEN, }) ).rejects.toThrow(/Invalid agent control token/u); await expect( t.query(api.signalRouting.listActiveIssues, { organizationId: orgId, projectId, token: BAD_TOKEN, }) ).rejects.toThrow(/Invalid agent control token/u); await expect( t.query(api.signalRouting.listProjects, { organizationId: orgId, token: BAD_TOKEN, }) ).rejects.toThrow(/Invalid agent control token/u); }); }); describe("signalRouting create and route", () => { test("create signal, list evidence, and list signals", async () => { const t = newTest(); const orgId = await ensureOrg(t); const projectId = await createProject(t, ID_A, "route-test"); const messageId = await seedMessage( t, orgId, "req-route", "The work-unit card should expose the generated PR." ); // Evidence appears before signal creation. const evidenceBefore = await t.query(api.signalRouting.listEvidence, { organizationId: orgId, token: TOKEN, }); expect(evidenceBefore).toHaveLength(1); expect(evidenceBefore[0]?.rawText).toBe( "The work-unit card should expose the generated PR." ); // Create the signal. const signalId = await createRoutingSignal(t, orgId, projectId, [ messageId, ]); // Evidence is consumed after signal creation. const evidenceAfter = await t.query(api.signalRouting.listEvidence, { organizationId: orgId, token: TOKEN, }); expect(evidenceAfter).toHaveLength(0); // Signal appears in the project list. const signals = await t.query(api.signalRouting.listSignals, { organizationId: orgId, projectId, token: TOKEN, }); expect(signals).toHaveLength(1); expect(signals[0]?._id).toBe(signalId); expect(signals[0]?.problemStatement.title).toBe("Deploy fails on staging"); }); test("attach signal to existing issue", async () => { const t = newTest(); const orgId = await ensureOrg(t); const projectId = await createProject(t, ID_A, "attach-test"); const messageId = await seedMessage(t, orgId, "req-attach", "problem"); const signalId = await createRoutingSignal(t, orgId, projectId, [ messageId, ]); // Create an issue via the existing projectIssues backend. const issueId = await t .withIdentity(identityA) .mutation(api.projectIssues.create, { body: "Existing issue body that is long enough.", projectId, title: "Existing UI issue", }); const result = await t.mutation(api.signalRouting.attachSignalToIssue, { issueId: issueId as string, organizationId: orgId, signalId: signalId as string, token: TOKEN, }); expect(result.alreadyAttached).toBe(false); // List active issues includes the attached issue. const issues = await t.query(api.signalRouting.listActiveIssues, { organizationId: orgId, projectId, token: TOKEN, }); expect(issues).toHaveLength(1); expect(issues[0]?._id).toBe(issueId); }); test("create issue from signal (attach-vs-create fallback)", async () => { const t = newTest(); const orgId = await ensureOrg(t); const projectId = await createProject(t, ID_A, "create-test"); const messageId = await seedMessage(t, orgId, "req-create", "new problem"); const signalId = await createRoutingSignal(t, orgId, projectId, [ messageId, ]); const result = await t.mutation(api.signalRouting.createIssueFromSignal, { organizationId: orgId, signalId: signalId as string, token: TOKEN, }); expect(result.created).toBe(true); expect(result.projectId).toBe(projectId); // The issue exists and is open. const issues = await t.query(api.signalRouting.listActiveIssues, { organizationId: orgId, projectId, token: TOKEN, }); expect(issues).toHaveLength(1); expect(issues[0]?.title).toBe("Deploy fails on staging"); }); }); describe("signalRouting idempotency", () => { test("duplicate attach retry returns existing without duplicate event", async () => { const t = newTest(); const orgId = await ensureOrg(t); const projectId = await createProject(t, ID_A, "idem-attach"); const messageId = await seedMessage(t, orgId, "req-idem-att", "problem"); const signalId = await createRoutingSignal(t, orgId, projectId, [ messageId, ]); const issueId = await t .withIdentity(identityA) .mutation(api.projectIssues.create, { body: "Existing issue body that is long enough for validation.", projectId, title: "Pre-existing issue", }); // First attach. const first = await t.mutation(api.signalRouting.attachSignalToIssue, { issueId: issueId as string, organizationId: orgId, signalId: signalId as string, token: TOKEN, }); expect(first.alreadyAttached).toBe(false); // Retry the same attach. const second = await t.mutation(api.signalRouting.attachSignalToIssue, { issueId: issueId as string, organizationId: orgId, signalId: signalId as string, token: TOKEN, }); expect(second.alreadyAttached).toBe(true); expect(second.attachmentId).toBe(first.attachmentId); // Verify only one signal.attached event was emitted (not duplicated). const events = await t .withIdentity(identityA) .query(api.projectIssues.events, { projectId }); const attached = events.filter((e: { kind: string }) => e.kind.includes("signal.attached") ); expect(attached).toHaveLength(1); }); test("duplicate create-from-signal retry returns existing issue", async () => { const t = newTest(); const orgId = await ensureOrg(t); const projectId = await createProject(t, ID_A, "idem-create"); const messageId = await seedMessage(t, orgId, "req-idem-crt", "problem"); const signalId = await createRoutingSignal(t, orgId, projectId, [ messageId, ]); // First create. const first = await t.mutation(api.signalRouting.createIssueFromSignal, { organizationId: orgId, signalId: signalId as string, token: TOKEN, }); expect(first.created).toBe(true); // Retry — should return existing, not create a new issue. const second = await t.mutation(api.signalRouting.createIssueFromSignal, { organizationId: orgId, signalId: signalId as string, token: TOKEN, }); expect(second.created).toBe(false); expect(second.issueId).toBe(first.issueId); // Only one issue exists. const issues = await t.query(api.signalRouting.listActiveIssues, { organizationId: orgId, projectId, token: TOKEN, }); expect(issues).toHaveLength(1); }); test("duplicate signal creation is idempotent (sourceKey)", async () => { const t = newTest(); const orgId = await ensureOrg(t); const projectId = await createProject(t, ID_A, "idem-signal"); const messageId = await seedMessage(t, orgId, "req-idem-sig", "test"); const first = await t.mutation(api.signalRouting.createSignal, { messageIds: [messageId], organizationId: orgId, problemStatement, processedByAgentInstanceId: orgId, projectId, token: TOKEN, }); const second = await t.mutation(api.signalRouting.createSignal, { messageIds: [messageId], organizationId: orgId, problemStatement, processedByAgentInstanceId: orgId, projectId, token: TOKEN, }); expect(second.signalId).toBe(first.signalId); }); }); describe("signalRouting cross-project rejection", () => { test("attach rejects when signal and issue are in different projects", async () => { const t = newTest(); const orgId = await ensureOrg(t); const projectA = await createProject(t, ID_A, "cross-a"); const projectB = await createProject(t, ID_A, "cross-b"); const messageId = await seedMessage(t, orgId, "req-cross", "problem"); // Signal is scoped to project A. const signalId = await createRoutingSignal(t, orgId, projectA, [messageId]); // Issue is in project B. const issueId = await t .withIdentity(identityA) .mutation(api.projectIssues.create, { body: "Issue in project B with enough body text.", projectId: projectB, title: "Project B issue", }); await expect( t.mutation(api.signalRouting.attachSignalToIssue, { issueId: issueId as string, organizationId: orgId, signalId: signalId as string, token: TOKEN, }) ).rejects.toThrow(/must belong to the same project/u); }); test("attach rejects org-scoped signal (no projectId)", async () => { const t = newTest(); const orgId = await ensureOrg(t); const projectId = await createProject(t, ID_A, "org-scope-test"); const messageId = await seedMessage(t, orgId, "req-org", "problem"); // Create an org-scoped signal (no projectId). const signalId = await t.mutation(api.signalRouting.createSignal, { messageIds: [messageId], organizationId: orgId, problemStatement, processedByAgentInstanceId: orgId, token: TOKEN, }); const issueId = await t .withIdentity(identityA) .mutation(api.projectIssues.create, { body: "Issue body that is long enough for the validator.", projectId, title: "Some issue", }); await expect( t.mutation(api.signalRouting.attachSignalToIssue, { issueId: issueId as string, organizationId: orgId, signalId: signalId.signalId as string, token: TOKEN, }) ).rejects.toThrow(/not project-scoped/u); }); }); describe("signalRouting provenance", () => { test("exact message raw text is preserved through the routing loop", async () => { const t = newTest(); const orgId = await ensureOrg(t); const projectId = await createProject(t, ID_A, "prov-test"); const rawText = " The work-unit card should expose the generated PR. "; const messageId = await seedMessage(t, orgId, "req-prov", rawText); // Evidence shows exact raw text. const evidence = await t.query(api.signalRouting.listEvidence, { organizationId: orgId, token: TOKEN, }); expect(evidence[0]?.rawText).toBe(rawText); // Signal creation preserves exact snapshot. const signalId = await createRoutingSignal(t, orgId, projectId, [ messageId, ]); // Verify via the existing signals.get that the snapshot is exact. const composed = await t .withIdentity(identityA) .query(api.signals.get, { signalId }); expect(composed?.sources[0]?.rawTextSnapshot).toBe(rawText); }); }); describe("signalRouting begin issue", () => { test("begin transitions an open issue to queued", async () => { const t = newTest(); const orgId = await ensureOrg(t); const projectId = await createProject(t, ID_A, "begin-test"); const messageId = await seedMessage(t, orgId, "req-begin", "start this"); const signalId = await createRoutingSignal(t, orgId, projectId, [ messageId, ]); const result = await t.mutation(api.signalRouting.createIssueFromSignal, { organizationId: orgId, signalId: signalId as string, token: TOKEN, }); const status = await t.mutation(api.signalRouting.beginIssue, { issueId: result.issueId as string, organizationId: orgId, token: TOKEN, }); expect(status).toBe("queued"); }); test("begin rejects invalid token", async () => { const t = newTest(); const orgId = await ensureOrg(t); const projectId = await createProject(t, ID_A, "begin-auth"); const messageId = await seedMessage(t, orgId, "req-begin-auth", "msg"); const signalId = await createRoutingSignal(t, orgId, projectId, [ messageId, ]); const result = await t.mutation(api.signalRouting.createIssueFromSignal, { organizationId: orgId, signalId: signalId as string, token: TOKEN, }); await expect( t.mutation(api.signalRouting.beginIssue, { issueId: result.issueId as string, organizationId: orgId, token: BAD_TOKEN, }) ).rejects.toThrow(/Invalid agent control token/u); }); }); describe("signalRouting project context", () => { test("get project context returns project and documents", async () => { const t = newTest(); const orgId = await ensureOrg(t); const projectId = await createProject(t, ID_A, "ctx-test"); const context = await t.query(api.signalRouting.getProjectContext, { organizationId: orgId, projectId, token: TOKEN, }); expect(context.project._id).toBe(projectId); expect(context.project.name).toBe("ctx-test"); expect(context.contextDocuments.length).toBeGreaterThan(0); }); test("list projects returns organization projects", async () => { const t = newTest(); const orgId = await ensureOrg(t); const projectId = await createProject(t, ID_A, "list-proj-test"); const projects = await t.query(api.signalRouting.listProjects, { organizationId: orgId, token: TOKEN, }); expect(projects).toHaveLength(1); expect(projects[0]?._id).toBe(projectId); }); });