432 lines · 14.8 KB · TypeScript
RawDownload
import { ProjectId, ThreadId } from "@t3tools/contracts";import { describe, expect, it } from "vite-plus/test"; import { v2Project, v2ShellSnapshot, v2ThreadShell } from "./orchestrationV2TestFixtures.ts";import { applyShellStreamEvent, mergeShellSnapshotProjects } from "./shellReducer.ts"; const repositoryIdentity = {  canonicalKey: "github.com/example/repo",  locator: {    source: "git-remote" as const,    remoteName: "origin",    remoteUrl: "https://github.com/example/repo.git",  },}; const otherIdentity = {  canonicalKey: "github.com/example/other",  locator: {    source: "git-remote" as const,    remoteName: "origin",    remoteUrl: "https://github.com/example/other.git",  },}; describe("applyShellStreamEvent", () => {  it("ignores stale project updates without mutating the snapshot", () => {    const snapshotWithProject = {      ...v2ShellSnapshot,      snapshotSequence: 4,      projects: [v2Project],    };     for (const sequence of [3, 4]) {      const next = applyShellStreamEvent(snapshotWithProject, {        kind: "project.updated",        sequence,        project: { ...v2Project, title: "Stale Title" },      });       expect(next).toBe(snapshotWithProject);      expect(next.snapshotSequence).toBe(4);      expect(next.projects[0]?.title).toBe(v2Project.title);    }  });   it("applies project updates and removals", () => {    const updated = applyShellStreamEvent(v2ShellSnapshot, {      kind: "project.updated",      sequence: 1,      project: { ...v2Project, title: "Updated" },    });    expect(updated.projects[0]?.title).toBe("Updated");    expect(updated.snapshotSequence).toBe(1);     const removed = applyShellStreamEvent(updated, {      kind: "project.removed",      sequence: 2,      projectId: ProjectId.make(v2Project.id),    });    expect(removed.projects).toEqual([]);  });   it("keeps prior repositoryIdentity when a delta arrives with null identity", () => {    const withIdentity = applyShellStreamEvent(v2ShellSnapshot, {      kind: "project.updated",      sequence: 1,      project: { ...v2Project, repositoryIdentity },    });    expect(withIdentity.projects[0]?.repositoryIdentity).toEqual(repositoryIdentity);     const withoutIdentity = applyShellStreamEvent(withIdentity, {      kind: "project.updated",      sequence: 2,      project: { ...v2Project, title: "Still same root", repositoryIdentity: null },    });    expect(withoutIdentity.projects[0]?.title).toBe("Still same root");    expect(withoutIdentity.projects[0]?.repositoryIdentity).toEqual(repositoryIdentity);  });   it("does not keep prior repositoryIdentity after a workspace root move", () => {    const withIdentity = applyShellStreamEvent(v2ShellSnapshot, {      kind: "project.updated",      sequence: 1,      project: { ...v2Project, repositoryIdentity },    });     const moved = applyShellStreamEvent(withIdentity, {      kind: "project.updated",      sequence: 2,      project: {        ...v2Project,        workspaceRoot: "/tmp/other-root",        repositoryIdentity: null,      },    });    expect(moved.projects[0]?.workspaceRoot).toBe("/tmp/other-root");    expect(moved.projects[0]?.repositoryIdentity).toBeNull();  });   it("merges full snapshot projects without dropping known repositoryIdentity", () => {    const previous = {      ...v2ShellSnapshot,      projects: [{ ...v2Project, repositoryIdentity }],    };    const next = mergeShellSnapshotProjects(previous, {      ...v2ShellSnapshot,      snapshotSequence: 2,      projects: [{ ...v2Project, title: "Refreshed", repositoryIdentity: null }],    });    expect(next.projects[0]?.title).toBe("Refreshed");    expect(next.projects[0]?.repositoryIdentity).toEqual(repositoryIdentity);  });   it("authoritative lower-sequence reset replaces client-ahead structural state", () => {    const previous = {      ...v2ShellSnapshot,      snapshotSequence: 9,      projects: [{ ...v2Project, title: "Client ahead", repositoryIdentity }],      threads: [{ ...v2ThreadShell, title: "Local-only thread" }],    };    const next = mergeShellSnapshotProjects(previous, {      ...v2ShellSnapshot,      snapshotSequence: 3,      projects: [{ ...v2Project, title: "Server reset", repositoryIdentity: null }],      threads: [{ ...v2ThreadShell, title: "Server thread" }],    });     expect(next.snapshotSequence).toBe(3);    expect(next.projects[0]?.title).toBe("Server reset");    expect(next.projects[0]?.repositoryIdentity).toEqual(repositoryIdentity);    expect(next.threads[0]?.title).toBe("Server thread");  });   it("client-ahead survives lower marked enrichment then still resets on unmarked authoritative", () => {    const previous = {      ...v2ShellSnapshot,      snapshotSequence: 20,      projects: [{ ...v2Project, title: "Client ahead", repositoryIdentity }],      threads: [{ ...v2ThreadShell, title: "Local-only thread" }],    };    const serverSnapshot = {      ...v2ShellSnapshot,      snapshotSequence: 3,      projects: [{ ...v2Project, title: "Server reset", repositoryIdentity: null }],      threads: [{ ...v2ThreadShell, title: "Server thread" }],    };     // Lower-sequence marked enrichment is identity-only and must not replace structure.    const afterEnrichment = mergeShellSnapshotProjects(previous, serverSnapshot, {      resolvedRepositoryIdentityRoots: [v2Project.workspaceRoot],    });    expect(afterEnrichment.snapshotSequence).toBe(20);    expect(afterEnrichment.projects[0]?.title).toBe("Client ahead");    expect(afterEnrichment.projects[0]?.repositoryIdentity).toBeNull();    expect(afterEnrichment.threads[0]?.title).toBe("Local-only thread");     // Unmarked authoritative at the same lower sequence still performs the reset.    const afterAuthoritative = mergeShellSnapshotProjects(afterEnrichment, serverSnapshot);    expect(afterAuthoritative.snapshotSequence).toBe(3);    expect(afterAuthoritative.projects[0]?.title).toBe("Server reset");    expect(afterAuthoritative.projects[0]?.repositoryIdentity).toBeNull();    expect(afterAuthoritative.threads[0]?.title).toBe("Server thread");  });   it("authoritative null retains identity then same-sequence marked enrichment clears it", () => {    const previous = {      ...v2ShellSnapshot,      snapshotSequence: 9,      projects: [{ ...v2Project, title: "Client ahead", repositoryIdentity }],      threads: [{ ...v2ThreadShell, title: "Local-only thread" }],    };    const serverSnapshot = {      ...v2ShellSnapshot,      snapshotSequence: 3,      projects: [{ ...v2Project, title: "Server reset", repositoryIdentity: null }],      threads: [{ ...v2ThreadShell, title: "Server thread" }],    };     const afterAuthoritative = mergeShellSnapshotProjects(previous, serverSnapshot);    expect(afterAuthoritative.snapshotSequence).toBe(3);    expect(afterAuthoritative.projects[0]?.title).toBe("Server reset");    expect(afterAuthoritative.projects[0]?.repositoryIdentity).toEqual(repositoryIdentity);    expect(afterAuthoritative.threads[0]?.title).toBe("Server thread");     const afterEnrichment = mergeShellSnapshotProjects(afterAuthoritative, serverSnapshot, {      resolvedRepositoryIdentityRoots: [v2Project.workspaceRoot],    });    expect(afterEnrichment.snapshotSequence).toBe(3);    expect(afterEnrichment.projects[0]?.title).toBe("Server reset");    expect(afterEnrichment.projects[0]?.repositoryIdentity).toBeNull();    expect(afterEnrichment.threads[0]?.title).toBe("Server thread");  });   it("takes identity from a lower-sequence enrichment without reverting live thread state", () => {    const previous = {      ...v2ShellSnapshot,      snapshotSequence: 5,      projects: [{ ...v2Project, repositoryIdentity: null }],      threads: [{ ...v2ThreadShell, title: "Newest title" }],    };    const next = mergeShellSnapshotProjects(      previous,      {        ...v2ShellSnapshot,        snapshotSequence: 4,        projects: [{ ...v2Project, repositoryIdentity }],        threads: [{ ...v2ThreadShell, title: "Stale title" }],      },      { resolvedRepositoryIdentityRoots: [v2Project.workspaceRoot] },    );     expect(next.snapshotSequence).toBe(5);    expect(next.threads[0]?.title).toBe("Newest title");    expect(next.projects[0]?.repositoryIdentity).toEqual(repositoryIdentity);  });   it("treats higher-sequence compact enrichment as metadata-only", () => {    const previous = {      ...v2ShellSnapshot,      snapshotSequence: 5,      projects: [{ ...v2Project, repositoryIdentity: null }],      threads: [{ ...v2ThreadShell, title: "Newest title" }],    };    const next = mergeShellSnapshotProjects(      previous,      {        ...v2ShellSnapshot,        snapshotSequence: 8,        projects: [{ ...v2Project, repositoryIdentity }],        threads: [],        archivedThreads: [],      },      { resolvedRepositoryIdentityRoots: [v2Project.workspaceRoot] },    );     expect(next.snapshotSequence).toBe(5);    expect(next.threads[0]?.title).toBe("Newest title");    expect(next.projects[0]?.repositoryIdentity).toEqual(repositoryIdentity);  });   it("lower-sequence enrichment cannot resurrect empty structural state", () => {    const previous = {      ...v2ShellSnapshot,      snapshotSequence: 7,      projects: [],      threads: [],      archivedThreads: [],    };    const next = mergeShellSnapshotProjects(      previous,      {        ...v2ShellSnapshot,        snapshotSequence: 4,        projects: [{ ...v2Project, repositoryIdentity }],        threads: [v2ThreadShell],      },      { resolvedRepositoryIdentityRoots: [v2Project.workspaceRoot] },    );     expect(next.snapshotSequence).toBe(7);    expect(next.projects).toEqual([]);    expect(next.threads).toEqual([]);  });   it("resolved null clears identity only for the marked root", () => {    const otherProject = {      ...v2Project,      id: ProjectId.make("project-other"),      workspaceRoot: "/workspace/other",      repositoryIdentity: otherIdentity,    };    const previous = {      ...v2ShellSnapshot,      snapshotSequence: 5,      projects: [{ ...v2Project, repositoryIdentity }, otherProject],    };    const next = mergeShellSnapshotProjects(      previous,      {        ...v2ShellSnapshot,        snapshotSequence: 4,        projects: [          { ...v2Project, repositoryIdentity: null },          { ...otherProject, repositoryIdentity: null },        ],      },      { resolvedRepositoryIdentityRoots: [v2Project.workspaceRoot] },    );     expect(next.snapshotSequence).toBe(5);    expect(next.projects).toHaveLength(2);    expect(next.projects[0]?.repositoryIdentity).toBeNull();    expect(next.projects[1]?.repositoryIdentity).toEqual(otherIdentity);  });   it("unrelated unresolved roots retain identity in the same enrichment snapshot", () => {    const otherProject = {      ...v2Project,      id: ProjectId.make("project-other"),      workspaceRoot: "/workspace/other",      repositoryIdentity: otherIdentity,    };    const previous = {      ...v2ShellSnapshot,      snapshotSequence: 3,      projects: [{ ...v2Project, repositoryIdentity }, otherProject],      threads: [{ ...v2ThreadShell, title: "Current thread" }],    };    const next = mergeShellSnapshotProjects(      previous,      {        ...v2ShellSnapshot,        snapshotSequence: 3,        projects: [          { ...v2Project, repositoryIdentity },          { ...otherProject, repositoryIdentity: null },        ],        threads: [{ ...v2ThreadShell, title: "Stale thread" }],      },      { resolvedRepositoryIdentityRoots: [v2Project.workspaceRoot] },    );     expect(next.threads[0]?.title).toBe("Current thread");    expect(next.projects[0]?.repositoryIdentity).toEqual(repositoryIdentity);    expect(next.projects[1]?.repositoryIdentity).toEqual(otherIdentity);  });   it("clears identity for multiple roots resolved in one enrichment batch", () => {    const otherProject = {      ...v2Project,      id: ProjectId.make("project-other"),      workspaceRoot: "/workspace/other",      repositoryIdentity: otherIdentity,    };    const previous = {      ...v2ShellSnapshot,      snapshotSequence: 3,      projects: [{ ...v2Project, repositoryIdentity }, otherProject],    };    const next = mergeShellSnapshotProjects(      previous,      {        ...v2ShellSnapshot,        snapshotSequence: 3,        projects: [          { ...v2Project, repositoryIdentity: null },          { ...otherProject, repositoryIdentity: null },        ],      },      {        resolvedRepositoryIdentityRoots: [v2Project.workspaceRoot, otherProject.workspaceRoot],      },    );     expect(next.projects.map((project) => project.repositoryIdentity)).toEqual([null, null]);  });   it("does not replace a live identity with one from an unmarked lower-sequence enrichment", () => {    const liveIdentity = {      canonicalKey: "github.com/example/live",      locator: {        source: "git-remote" as const,        remoteName: "origin",        remoteUrl: "https://github.com/example/live.git",      },    };    const staleIdentity = {      canonicalKey: "github.com/example/stale",      locator: {        source: "git-remote" as const,        remoteName: "origin",        remoteUrl: "https://github.com/example/stale.git",      },    };    const next = mergeShellSnapshotProjects(      {        ...v2ShellSnapshot,        snapshotSequence: 5,        projects: [{ ...v2Project, repositoryIdentity: liveIdentity }],      },      {        ...v2ShellSnapshot,        snapshotSequence: 4,        projects: [{ ...v2Project, repositoryIdentity: staleIdentity }],      },      { resolvedRepositoryIdentityRoots: [] },    );     expect(next.snapshotSequence).toBe(5);    expect(next.projects[0]?.repositoryIdentity).toEqual(liveIdentity);  });   it("keeps archive deltas out of the normal shell cache", () => {    const archived = applyShellStreamEvent(v2ShellSnapshot, {      kind: "thread.updated",      sequence: 3,      location: "archive",      thread: { ...v2ThreadShell, archivedAt: v2ThreadShell.updatedAt },    });    expect(archived.threads).toEqual([]);    expect(archived.archivedThreads).toEqual([]);     const active = applyShellStreamEvent(archived, {      kind: "thread.updated",      sequence: 4,      location: "active",      thread: v2ThreadShell,    });    expect(active.threads).toHaveLength(1);    expect(active.archivedThreads).toEqual([]);  });   it("removes a thread from either collection", () => {    const next = applyShellStreamEvent(v2ShellSnapshot, {      kind: "thread.removed",      sequence: 5,      location: "active",      threadId: ThreadId.make(v2ThreadShell.id),    });    expect(next.threads).toEqual([]);    expect(next.snapshotSequence).toBe(5);  });   it("leaves unknown future events unchanged", () => {    const next = applyShellStreamEvent(v2ShellSnapshot, {      kind: "future.event",      sequence: 99,    } as never);     expect(next).toBe(v2ShellSnapshot);  });});