Skip to content
Source
Sign inSign up
Open History to see the last commit

projectImport.logic.test.ts

254 lines8.4 KB

1import { EnvironmentId, ProjectId, type AgentSessionProjectCandidate } from "@t3tools/contracts";
2import { describe, expect, it } from "vite-plus/test";
3​
4import {
5 groupOnboardingProjects,
6 isNewOnboardingProject,
7 partitionOnboardingProjects,
8 onboardingProjectKey,
9 resolveOnboardingLandingProject,
10 resolveOnboardingProjectId,
11} from "./projectImport.logic";
12​
13const now = Date.parse("2026-08-22T12:00:00.000Z");
14​
15function candidate(
16 path: string,
17 overrides: Partial<AgentSessionProjectCandidate> = {},
18): AgentSessionProjectCandidate {
19 return {
20 title: path.split("/").at(-1) ?? path,
21 path,
22 sources: ["codex"],
23 threadCount: 3,
24 lastActiveAt: "2026-08-20T12:00:00.000Z",
25 alreadyImported: false,
26 git: { remoteKey: null, repository: null },
27 ...overrides,
28 };
30​
31const github = (repository: string) => ({
32 remoteKey: `github.com/${repository.toLowerCase()}`,
33 repository,
34});
35​
36describe("partitionOnboardingProjects", () => {
37 it("keeps existing projects available for thread history import", () => {
38 const imported = candidate("/projects/current", { alreadyImported: true });
39 const available = candidate("/projects/other");
40​
41 expect(partitionOnboardingProjects([imported, available], now)).toEqual({
42 available: [imported, available],
43 recent: [imported, available],
44 });
45 });
46​
47 it("keeps projects older than 30 days out of the default selection", () => {
48 const recent = candidate("/projects/recent");
49 const older = candidate("/projects/older", {
50 lastActiveAt: "2026-07-01T12:00:00.000Z",
51 });
52​
53 expect(partitionOnboardingProjects([recent, older], now)).toEqual({
54 available: [recent, older],
55 recent: [recent],
56 });
57 });
58​
59 it("keeps non-git folders and thin histories out of the default selection", () => {
60 const repo = candidate("/projects/repo");
61 const folder = candidate("/projects/folder", { git: null });
62 const thin = candidate("/projects/thin", { threadCount: 2 });
63​
64 expect(partitionOnboardingProjects([repo, folder, thin], now).recent).toEqual([repo]);
65 });
66​
67 it("selects candidates from servers that do not report git identity", () => {
68 const { git: _git, ...legacy } = candidate("/projects/legacy");
69​
70 expect(partitionOnboardingProjects([legacy], now).recent).toEqual([legacy]);
71 });
72});
73​
74describe("groupOnboardingProjects", () => {
75 it("groups clones by origin, keeps local repos separate, and folds non-git folders away", () => {
76 const main = candidate("/code/t3code", {
77 git: github("pingdotgg/t3code"),
78 threadCount: 79,
79 lastActiveAt: "2026-08-21T12:00:00.000Z",
80 });
81 const clone = candidate("/code/clones/t3code-2", {
82 git: github("pingdotgg/t3code"),
83 threadCount: 13,
84 lastActiveAt: "2026-08-10T12:00:00.000Z",
85 });
86 const older = candidate("/code/fleet", {
87 git: github("t3dotgg/fleet"),
88 threadCount: 295,
89 lastActiveAt: "2026-08-22T00:00:00.000Z",
90 });
91 const local = candidate("/code/scratch-repo", { title: "scratch-repo" });
92 const folder = candidate("/tmp/notes", { git: null });
93​
94 const grouped = groupOnboardingProjects([main, clone, older, local, folder]);
95​
96 expect(grouped.other).toEqual([folder]);
97 expect(
98 grouped.repositories.map((group) => ({
99 label: group.label,
100 paths: group.candidates.map((item) => item.path),
101 threadCount: group.threadCount,
102 lastActiveAt: group.lastActiveAt,
103 })),
104 ).toEqual([
105 {
106 label: "t3dotgg/fleet",
107 paths: ["/code/fleet"],
108 threadCount: 295,
109 lastActiveAt: "2026-08-22T00:00:00.000Z",
110 },
111 {
112 label: "pingdotgg/t3code",
113 paths: ["/code/t3code", "/code/clones/t3code-2"],
114 threadCount: 92,
115 lastActiveAt: "2026-08-21T12:00:00.000Z",
116 },
117 {
118 label: "scratch-repo",
119 paths: ["/code/scratch-repo"],
120 threadCount: 3,
121 lastActiveAt: "2026-08-20T12:00:00.000Z",
122 },
123 ]);
124 });
125​
126 // regression: #10547 project import broke on servers that predate git identity
127 it("lists candidates without git identity as standalone repositories", () => {
128 const { git: _git, ...legacy } = candidate("/code/legacy", { title: "legacy" });
129​
130 const grouped = groupOnboardingProjects([legacy]);
131​
132 expect(grouped.other).toEqual([]);
133 expect(grouped.repositories.map((group) => [group.label, group.repository])).toEqual([
134 ["legacy", null],
135 ]);
136 });
137});
138​
139describe("resolveOnboardingProjectId", () => {
140 const localEnvironmentId = EnvironmentId.make("local");
141 const remoteEnvironmentId = EnvironmentId.make("remote");
142 const localProjectId = ProjectId.make("local-project");
143​
144 it("uses the scanned project ID when the client still has an older project at that root", () => {
145 const scanned = candidate("/projects/repo", { projectId: localProjectId });
146 expect(
147 resolveOnboardingProjectId(
148 [
149 {
150 id: ProjectId.make("stale-project"),
151 environmentId: localEnvironmentId,
152 workspaceRoot: "/projects/repo",
153 },
154 ],
155 localEnvironmentId,
156 scanned,
157 ),
158 ).toBe(localProjectId);
159 expect(isNewOnboardingProject([])(localEnvironmentId, scanned)).toBe(false);
160 });
161​
162 it("finds an existing project by normalized root in the target environment", () => {
163 const projects = [
164 {
165 id: ProjectId.make("remote-project"),
166 environmentId: remoteEnvironmentId,
167 workspaceRoot: "C:\\Work\\Repo",
168 },
169 {
170 id: localProjectId,
171 environmentId: localEnvironmentId,
172 workspaceRoot: "C:\\Work\\Repo\\",
173 },
174 ];
175 expect(
176 resolveOnboardingProjectId(projects, localEnvironmentId, candidate("c:/work/repo")),
177 ).toBe(localProjectId);
178 expect(isNewOnboardingProject(projects)(localEnvironmentId, candidate("c:/work/repo"))).toBe(
179 false,
180 );
181 });
182​
183 it("does not reuse a project from another environment", () => {
184 const projects = [
185 {
186 id: ProjectId.make("remote-project"),
187 environmentId: remoteEnvironmentId,
188 workspaceRoot: "/projects/repo",
189 },
190 ];
191 expect(
192 resolveOnboardingProjectId(projects, localEnvironmentId, candidate("/projects/repo")),
193 ).toBeNull();
194 expect(isNewOnboardingProject(projects)(localEnvironmentId, candidate("/projects/repo"))).toBe(
195 true,
196 );
197 });
198});
199​
200describe("resolveOnboardingLandingProject", () => {
201 it("prefers a partial first import that added history", () => {
202 expect(
203 resolveOnboardingLandingProject(
204 ["/projects/partial", "/projects/complete"],
205 new Map([["/projects/partial", "partial"]]),
206 new Map([["/projects/complete", "complete"]]),
207 ),
208 ).toBe("partial");
209 });
210​
211 it("uses a completed zero-history project when no import added history", () => {
212 expect(
213 resolveOnboardingLandingProject(
214 ["/projects/empty", "/projects/failed"],
215 new Map(),
216 new Map([["/projects/empty", "empty"]]),
217 ),
218 ).toBe("empty");
219 });
220​
221 it("ignores cached successes outside the current retry selection", () => {
222 expect(
223 resolveOnboardingLandingProject(
224 ["/projects/current"],
225 new Map([["/projects/previous", "previous"]]),
226 new Map([
227 ["/projects/previous", "previous"],
228 ["/projects/current", "current"],
229 ]),
230 ),
231 ).toBe("current");
232 });
233});
234​
235describe("projects on multiple computers", () => {
236 it("keeps identical paths independent when selecting a landing project after partial imports", () => {
237 const first = onboardingProjectKey(EnvironmentId.make("first"), "/code/app");
238 const second = onboardingProjectKey(EnvironmentId.make("second"), "/code/app");
239 const completed = new Map([[first, "first-project"]]);
240 expect(completed.has(second)).toBe(false);
241 expect(resolveOnboardingLandingProject([second], completed, completed)).toBeUndefined();
242 completed.set(second, "second-project");
243 expect(resolveOnboardingLandingProject([second, first], completed, completed)).toBe(
244 "second-project",
245 );
246 });
247​
248 // regression: #10465 multi-computer onboarding mixed up which computer a project came from
249 it("preserves computer identity when choosing recent projects", () => {
250 const first = { ...candidate("/code/app"), environmentId: "first" };
251 const second = { ...candidate("/code/app"), environmentId: "second" };
252 expect(partitionOnboardingProjects([first, second], now).recent).toEqual([first, second]);
253 });
254});