feat: migrate legacy projects through binding search

This commit is contained in:
2026-07-11 23:22:07 +08:00
parent 53998d2651
commit 530fcdd2b7
11 changed files with 873 additions and 31 deletions
+94
View File
@@ -361,6 +361,100 @@ describe("trigger full lifecycle (integration)", () => {
expect(cardHeaderTitle(rt.sentPatches.at(-1))).toBe("已绑定项目");
});
it("uses unbound-chat mention text to search bindable projects", async () => {
await seedOnboardingUser("u-onboard-search", "ou_onboard_search", "OWNER");
const folder = await prisma.folder.create({
data: { organizationId: DEFAULT_ORG_ID, name: "物理竞赛" },
});
await prisma.project.createMany({
data: [
{
id: "p-search-newton",
organizationId: DEFAULT_ORG_ID,
folderId: folder.id,
name: "牛顿力学专题",
workspaceDir: join(await tempWorkspaceRoot(), "p-search-newton"),
},
{
id: "p-search-optics",
organizationId: DEFAULT_ORG_ID,
folderId: folder.id,
name: "几何光学专题",
workspaceDir: join(await tempWorkspaceRoot(), "p-search-optics"),
},
],
});
const trigger = makeTriggerHandler({
prisma,
settings,
logger: silentLogger,
runAgent,
projectWorkspaceRoot: await tempWorkspaceRoot(),
});
await trigger(makeEvent("chat-onboard-search", "@_user_1 牛顿", "ou_onboard_search"), rt);
expect(cardActionValues(rt.sentCards[0])).toContainEqual({
project_onboarding: {
action: "bind_project",
organization_id: DEFAULT_ORG_ID,
project_id: "p-search-newton",
},
});
expect(cardActionValues(rt.sentCards[0])).not.toContainEqual(expect.objectContaining({
project_onboarding: expect.objectContaining({ project_id: "p-search-optics" }),
}));
expect(JSON.stringify(rt.sentCards[0])).toContain("牛顿");
expect(runAgentCalls).toHaveLength(0);
});
it("continues searching past unauthorized matches for a manageable project", async () => {
await seedOnboardingUser("u-onboard-page", "ou_onboard_page", "MEMBER");
const workspaceRoot = await tempWorkspaceRoot();
await prisma.project.createMany({
data: [
...Array.from({ length: 20 }, (_, index) => ({
id: `z-search-denied-${String(index).padStart(2, "0")}`,
organizationId: DEFAULT_ORG_ID,
name: `迁移项目 ${index}`,
workspaceDir: join(workspaceRoot, `denied-${index}`),
})),
{
id: "a-search-allowed",
organizationId: DEFAULT_ORG_ID,
name: "迁移项目 可管理",
workspaceDir: join(workspaceRoot, "allowed"),
},
],
});
await prisma.permissionGrant.create({
data: {
resourceType: "PROJECT",
resourceId: "a-search-allowed",
principalType: "USER",
principalId: "ou_onboard_page",
role: "MANAGE",
},
});
const trigger = makeTriggerHandler({
prisma,
settings,
logger: silentLogger,
runAgent,
projectWorkspaceRoot: workspaceRoot,
});
await trigger(makeEvent("chat-onboard-page", "@_user_1 迁移", "ou_onboard_page"), rt);
expect(cardActionValues(rt.sentCards[0])).toContainEqual({
project_onboarding: {
action: "bind_project",
organization_id: DEFAULT_ORG_ID,
project_id: "a-search-allowed",
},
});
});
it("batches quick text messages from the same chat and sender into one run", async () => {
await seedProject("proj-1b", "chat-1b");
const trigger = makeTriggerHandler({