Convex-only Slice 1: clients talk to Convex, Flue is a private worker (#20)
This commit is contained in:
@@ -3,7 +3,6 @@ import { convexTest } from "convex-test";
|
||||
import { anyApi } from "convex/server";
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
import { internal } from "./_generated/api";
|
||||
import schema from "./schema";
|
||||
|
||||
declare global {
|
||||
@@ -14,211 +13,61 @@ declare global {
|
||||
|
||||
const modules = import.meta.glob("./**/*.ts");
|
||||
const api = anyApi;
|
||||
const identity = { tokenIdentifier: "https://convex.test|slice-one" };
|
||||
|
||||
describe("Slice 1 Work routing", () => {
|
||||
test("creates one proposed Work and preserves exact source text", async () => {
|
||||
const t = convexTest({ schema, modules });
|
||||
const organization = await t
|
||||
.withIdentity(identity)
|
||||
.mutation(api.organizations.ensurePersonalOrganization, {});
|
||||
const project = await t
|
||||
.withIdentity(identity)
|
||||
.mutation(internal.projects.persistPublicGitImport, {
|
||||
remote: { defaultBranch: "main", documents: [], warnings: [] },
|
||||
source: {
|
||||
host: "github.com",
|
||||
normalizedUrl: "https://github.com/example/slice-one",
|
||||
projectName: "slice-one",
|
||||
repositoryPath: "example/slice-one",
|
||||
url: "https://github.com/example/slice-one",
|
||||
},
|
||||
userId: identity.tokenIdentifier,
|
||||
describe("works", () => {
|
||||
test("creates one proposed Work and one normalized attachment", async () => {
|
||||
const t = convexTest({ modules, schema });
|
||||
const fixture = await t.run(async (ctx) => {
|
||||
const organizationId = await ctx.db.insert("organizations", {
|
||||
createdAt: 1,
|
||||
createdBy: "user",
|
||||
kind: "personal",
|
||||
name: "Personal",
|
||||
});
|
||||
const rawText = " Add a phone-ready Slice 1 experience. ";
|
||||
const message = await t
|
||||
.withIdentity(identity)
|
||||
.mutation(api.conversationMessages.beginUserMessage, {
|
||||
clientRequestId: "slice-one-request",
|
||||
organizationId: organization._id,
|
||||
rawText,
|
||||
const projectId = await ctx.db.insert("projects", {
|
||||
createdAt: 1,
|
||||
name: "Zopu",
|
||||
normalizedSourceUrl: "https://example.com/zopu",
|
||||
organizationId,
|
||||
repositoryPath: "puter/zopu",
|
||||
sourceHost: "example.com",
|
||||
sourceUrl: "https://example.com/zopu",
|
||||
updatedAt: 1,
|
||||
});
|
||||
await t
|
||||
.withIdentity(identity)
|
||||
.mutation(api.conversationMessages.markAdmitted, {
|
||||
clientRequestId: "slice-one-request",
|
||||
organizationId: organization._id,
|
||||
submissionId: "slice-one-submission",
|
||||
const conversationId = await ctx.db.insert("conversations", {
|
||||
createdAt: 1,
|
||||
organizationId,
|
||||
});
|
||||
const signal = await t.mutation(api.signalRouting.createSignal, {
|
||||
messageIds: [message.messageId],
|
||||
organizationId: organization._id,
|
||||
problemStatement: {
|
||||
constraints: ["Mobile web first"],
|
||||
desiredOutcome: "The Slice 1 loop works on a phone.",
|
||||
summary: "The current product is not ready for phone testing.",
|
||||
title: "Make Slice 1 phone-ready",
|
||||
},
|
||||
processedByAgentInstanceId: organization._id,
|
||||
projectId: project.id,
|
||||
token: env.FLUE_DB_TOKEN,
|
||||
const signalId = await ctx.db.insert("signals", {
|
||||
conversationId,
|
||||
createdAt: 1,
|
||||
desiredOutcome: "A working Slice 1",
|
||||
organizationId,
|
||||
processedByAgentInstanceId: String(organizationId),
|
||||
processedByAgentName: "zopu",
|
||||
projectId,
|
||||
sourceKey: "source-1",
|
||||
summary: "Slice 1 needs work",
|
||||
title: "Finish Slice 1",
|
||||
});
|
||||
return { organizationId, signalId };
|
||||
});
|
||||
|
||||
const first = await t.mutation(api.works.createFromSignal, {
|
||||
organizationId: organization._id,
|
||||
signalId: signal.signalId,
|
||||
organizationId: fixture.organizationId,
|
||||
signalId: fixture.signalId,
|
||||
token: env.FLUE_DB_TOKEN,
|
||||
});
|
||||
const repeated = await t.mutation(api.works.createFromSignal, {
|
||||
organizationId: organization._id,
|
||||
signalId: signal.signalId,
|
||||
const second = await t.mutation(api.works.createFromSignal, {
|
||||
organizationId: fixture.organizationId,
|
||||
signalId: fixture.signalId,
|
||||
token: env.FLUE_DB_TOKEN,
|
||||
});
|
||||
expect(repeated).toEqual({ created: false, workId: first.workId });
|
||||
|
||||
const works = await t
|
||||
.withIdentity(identity)
|
||||
.query(api.works.listForProject, { projectId: project.id });
|
||||
expect(works).toHaveLength(1);
|
||||
expect(works[0]?.status).toBe("proposed");
|
||||
expect(works[0]?.signals[0]?.sources[0]?.rawText).toBe(rawText);
|
||||
});
|
||||
|
||||
test("keeps casual conversation out of Work", async () => {
|
||||
const t = convexTest({ schema, modules });
|
||||
const organization = await t
|
||||
.withIdentity(identity)
|
||||
.mutation(api.organizations.ensurePersonalOrganization, {});
|
||||
const project = await t
|
||||
.withIdentity(identity)
|
||||
.mutation(internal.projects.persistPublicGitImport, {
|
||||
remote: { defaultBranch: "main", documents: [], warnings: [] },
|
||||
source: {
|
||||
host: "git.openputer.com",
|
||||
normalizedUrl: "https://git.openputer.com/puter/zopu-code",
|
||||
projectName: "zopu-code",
|
||||
repositoryPath: "puter/zopu-code",
|
||||
url: "https://git.openputer.com/puter/zopu-code",
|
||||
},
|
||||
userId: identity.tokenIdentifier,
|
||||
});
|
||||
await t
|
||||
.withIdentity(identity)
|
||||
.mutation(api.conversationMessages.beginUserMessage, {
|
||||
clientRequestId: "slice-one-casual",
|
||||
organizationId: organization._id,
|
||||
rawText: "Yo, how are you?",
|
||||
});
|
||||
await t
|
||||
.withIdentity(identity)
|
||||
.mutation(api.conversationMessages.markAdmitted, {
|
||||
clientRequestId: "slice-one-casual",
|
||||
organizationId: organization._id,
|
||||
submissionId: "slice-one-casual-submission",
|
||||
});
|
||||
|
||||
const works = await t
|
||||
.withIdentity(identity)
|
||||
.query(api.works.listForProject, { projectId: project.id });
|
||||
expect(works).toEqual([]);
|
||||
});
|
||||
|
||||
test("attaches another Signal to existing Work idempotently", async () => {
|
||||
const t = convexTest({ schema, modules });
|
||||
const organization = await t
|
||||
.withIdentity(identity)
|
||||
.mutation(api.organizations.ensurePersonalOrganization, {});
|
||||
const project = await t
|
||||
.withIdentity(identity)
|
||||
.mutation(internal.projects.persistPublicGitImport, {
|
||||
remote: { defaultBranch: "main", documents: [], warnings: [] },
|
||||
source: {
|
||||
host: "git.openputer.com",
|
||||
normalizedUrl: "https://git.openputer.com/puter/zopu-code",
|
||||
projectName: "zopu-code",
|
||||
repositoryPath: "puter/zopu-code",
|
||||
url: "https://git.openputer.com/puter/zopu-code",
|
||||
},
|
||||
userId: identity.tokenIdentifier,
|
||||
});
|
||||
|
||||
const seedSignal = async (
|
||||
clientRequestId: string,
|
||||
rawText: string,
|
||||
title: string
|
||||
) => {
|
||||
const message = await t
|
||||
.withIdentity(identity)
|
||||
.mutation(api.conversationMessages.beginUserMessage, {
|
||||
clientRequestId,
|
||||
organizationId: organization._id,
|
||||
rawText,
|
||||
});
|
||||
await t
|
||||
.withIdentity(identity)
|
||||
.mutation(api.conversationMessages.markAdmitted, {
|
||||
clientRequestId,
|
||||
organizationId: organization._id,
|
||||
submissionId: `${clientRequestId}-submission`,
|
||||
});
|
||||
return await t.mutation(api.signalRouting.createSignal, {
|
||||
messageIds: [message.messageId],
|
||||
organizationId: organization._id,
|
||||
problemStatement: {
|
||||
constraints: [],
|
||||
desiredOutcome: "The Slice 1 phone flow is polished.",
|
||||
summary: "The messages describe the same desired outcome.",
|
||||
title,
|
||||
},
|
||||
processedByAgentInstanceId: organization._id,
|
||||
projectId: project.id,
|
||||
token: env.FLUE_DB_TOKEN,
|
||||
});
|
||||
};
|
||||
|
||||
const firstSignal = await seedSignal(
|
||||
"slice-one-first",
|
||||
"Polish the Slice 1 phone experience.",
|
||||
"Polish Slice 1"
|
||||
expect(first.created).toBe(true);
|
||||
expect(second).toEqual({ created: false, workId: first.workId });
|
||||
const attachments = await t.run(async (ctx) =>
|
||||
ctx.db.query("signalWorkAttachments").collect()
|
||||
);
|
||||
const secondSignal = await seedSignal(
|
||||
"slice-one-second",
|
||||
"Make the same Slice 1 experience easier to inspect on mobile.",
|
||||
"Improve Slice 1 inspection"
|
||||
);
|
||||
const created = await t.mutation(api.works.createFromSignal, {
|
||||
organizationId: organization._id,
|
||||
signalId: firstSignal.signalId,
|
||||
token: env.FLUE_DB_TOKEN,
|
||||
});
|
||||
const firstAttach = await t.mutation(api.works.attachSignalToWork, {
|
||||
organizationId: organization._id,
|
||||
signalId: secondSignal.signalId,
|
||||
token: env.FLUE_DB_TOKEN,
|
||||
workId: created.workId,
|
||||
});
|
||||
const repeatedAttach = await t.mutation(api.works.attachSignalToWork, {
|
||||
organizationId: organization._id,
|
||||
signalId: secondSignal.signalId,
|
||||
token: env.FLUE_DB_TOKEN,
|
||||
workId: created.workId,
|
||||
});
|
||||
|
||||
expect(firstAttach).toEqual({ attached: true, workId: created.workId });
|
||||
expect(repeatedAttach).toEqual({
|
||||
attached: false,
|
||||
workId: created.workId,
|
||||
});
|
||||
|
||||
const works = await t
|
||||
.withIdentity(identity)
|
||||
.query(api.works.listForProject, { projectId: project.id });
|
||||
expect(works).toHaveLength(1);
|
||||
expect(works[0]?.signals).toHaveLength(2);
|
||||
expect(
|
||||
works[0]?.events.filter(
|
||||
(event: { readonly kind: string }) => event.kind === "signal.attached"
|
||||
)
|
||||
).toHaveLength(1);
|
||||
expect(attachments).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user