Files
zopu-code/packages/backend/convex/projectIssueRequests.smoke.test.ts

178 lines
5.7 KiB
TypeScript

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<string, () => Promise<unknown>>;
}
}
const modules = import.meta.glob("./**/*.ts");
const api = anyApi;
const ID_A = "https://convex.test|project-request-a";
const ID_B = "https://convex.test|project-request-b";
const identityA = { tokenIdentifier: ID_A };
const identityB = { tokenIdentifier: ID_B };
const createProject = async (
t: TestConvex<typeof schema>
): Promise<Id<"projects">> => {
await t
.withIdentity(identityA)
.mutation(api.organizations.ensurePersonalOrganization, {});
const outcome = await t
.withIdentity(identityA)
.mutation(internal.projects.persistPublicGitImport, {
userId: ID_A,
source: {
host: "github.com",
normalizedUrl: "https://github.com/test/project-request",
projectName: "project-request",
repositoryPath: "test/project-request",
url: "https://github.com/test/project-request",
},
remote: {
defaultBranch: "main",
documents: [
{
content: "# Project Request\n",
kind: "readme" as const,
path: "README.md",
},
],
warnings: [],
},
});
return outcome.id as unknown as Id<"projects">;
};
describe("project issue request smoke", () => {
test("project Signal becomes a queued project issue with durable evidence", async () => {
const t = convexTest({ schema, modules });
const projectId = await createProject(t);
const organization = await t
.withIdentity(identityA)
.mutation(api.organizations.ensurePersonalOrganization, {});
const begun = await t
.withIdentity(identityA)
.mutation(api.conversationMessages.beginUserMessage, {
clientRequestId: "project-request-1",
organizationId: organization._id,
rawText: "Staging deploys fail during DNS resolution.",
});
await t
.withIdentity(identityA)
.mutation(api.conversationMessages.markAdmitted, {
clientRequestId: "project-request-1",
organizationId: organization._id,
submissionId: "submission-project-request-1",
});
const signal = await t
.withIdentity(identityA)
.mutation(api.signals.createFromMessages, {
conversationId: organization._id,
messageIds: [begun.messageId],
organizationId: organization._id,
problemStatement: {
constraints: ["Do not change production"],
desiredOutcome: "Staging deploys successfully",
summary: "The staging deploy fails during DNS resolution.",
title: "Fix the staging deploy DNS failure",
},
processedBy: {
agentInstanceId: String(organization._id),
agentName: "zopu",
},
projectId,
});
const created = await t
.withIdentity(identityA)
.mutation(api.projectIssues.createFromSignal, {
signalId: signal.signalId,
});
const issue = await t.run(async (ctx) => ctx.db.get(created.issueId));
expect(issue).toMatchObject({
body: expect.stringContaining("Source Signal"),
projectId,
status: "open",
title: "Fix the staging deploy DNS failure",
});
const status = await t
.withIdentity(identityA)
.mutation(api.projectIssues.begin, { issueId: created.issueId });
expect(status).toBe("queued");
const events = await t.run(async (ctx) =>
ctx.db
.query("projectEvents")
.withIndex("by_project_and_createdAt", (q) =>
q.eq("projectId", projectId)
)
.collect()
);
expect(events.map((event) => event.kind)).toEqual([
"project.connected",
"issue.created",
"issue.queued",
]);
});
test("a different organization cannot consume the project Signal", async () => {
const t = convexTest({ schema, modules });
const projectId = await createProject(t);
await t
.withIdentity(identityB)
.mutation(api.organizations.ensurePersonalOrganization, {});
const organization = await t
.withIdentity(identityA)
.mutation(api.organizations.ensurePersonalOrganization, {});
const begun = await t
.withIdentity(identityA)
.mutation(api.conversationMessages.beginUserMessage, {
clientRequestId: "project-request-cross-org",
organizationId: organization._id,
rawText: "Private project request",
});
await t
.withIdentity(identityA)
.mutation(api.conversationMessages.markAdmitted, {
clientRequestId: "project-request-cross-org",
organizationId: organization._id,
submissionId: "submission-project-request-cross-org",
});
const signal = await t
.withIdentity(identityA)
.mutation(api.signals.createFromMessages, {
conversationId: organization._id,
messageIds: [begun.messageId],
organizationId: organization._id,
problemStatement: {
constraints: [],
desiredOutcome: "Keep the request private",
summary: "This request belongs to the first organization.",
title: "Private project request",
},
processedBy: {
agentInstanceId: String(organization._id),
agentName: "zopu",
},
projectId,
});
await expect(
t.withIdentity(identityB).mutation(api.projectIssues.createFromSignal, {
signalId: signal.signalId,
})
).rejects.toThrow(/membership required/u);
});
});