Skip to content

Commit c2b56f6

Browse files
committed
test: cover runtime meta failures
1 parent 8cad0ee commit c2b56f6

File tree

2 files changed

+95
-4
lines changed

2 files changed

+95
-4
lines changed

src/daemon/runtime-cache.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,22 @@ export interface RuntimeCache {
8686

8787
export function createRuntimeCache(opts: {
8888
readonly onRefresh?: (snapshot: RuntimeSnapshot) => void;
89+
readonly deps?: {
90+
readonly readProjectsRegistry?: typeof readProjectsRegistry;
91+
readonly buildProjectViews?: typeof buildProjectViews;
92+
readonly serializeProjectView?: typeof serializeProjectView;
93+
readonly resolveProjectMeta?: typeof resolveProjectMeta;
94+
};
8995
}): RuntimeCache {
96+
const deps = {
97+
readProjectsRegistry:
98+
opts.deps?.readProjectsRegistry ?? readProjectsRegistry,
99+
buildProjectViews: opts.deps?.buildProjectViews ?? buildProjectViews,
100+
serializeProjectView:
101+
opts.deps?.serializeProjectView ?? serializeProjectView,
102+
resolveProjectMeta: opts.deps?.resolveProjectMeta ?? resolveProjectMeta,
103+
} as const;
104+
90105
let snapshot: RuntimeSnapshot | null = null;
91106
let refreshTask: Promise<void> | null = null;
92107
let pending = false;
@@ -195,12 +210,12 @@ export function createRuntimeCache(opts: {
195210
if (!snapshot) {
196211
await refresh({ reason: "projects" });
197212
}
198-
const registry = await readProjectsRegistry();
213+
const registry = await deps.readProjectsRegistry();
199214
const runtime = filterRuntimeProjects({
200215
runtime: snapshot?.runtime ?? [],
201216
includeGlobal,
202217
});
203-
const views = await buildProjectViews({
218+
const views = await deps.buildProjectViews({
204219
registryProjects: registry.projects,
205220
runtime,
206221
runtimeOk: health.ok,
@@ -224,7 +239,7 @@ export function createRuntimeCache(opts: {
224239
return null;
225240
}
226241
try {
227-
return await resolveProjectMeta({
242+
return await deps.resolveProjectMeta({
228243
projectName: reg.name,
229244
repoRoot: reg.repoRoot,
230245
projectDir: reg.projectDir,
@@ -249,7 +264,7 @@ export function createRuntimeCache(opts: {
249264
runtime_reset_at: runtimeMeta.lastResetAt,
250265
runtime_reset_count: runtimeMeta.resetCount,
251266
projects: views.map((view, i) => ({
252-
...serializeProjectView(view),
267+
...deps.serializeProjectView(view),
253268
...(includeMeta ? { meta: metas[i] ?? null } : {}),
254269
})),
255270
};

tests/runtime-cache.test.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { beforeEach, expect, mock, test } from "bun:test";
22

3+
import { HACK_PROJECT_DIR_PRIMARY } from "../src/constants.ts";
4+
import type { ProjectView } from "../src/lib/project-views.ts";
5+
import type { RegisteredProject } from "../src/lib/projects-registry.ts";
36
import type { RuntimeProject } from "../src/lib/runtime-projects.ts";
47

58
const runtimeQueue: Array<{
@@ -109,6 +112,79 @@ test("runtime cache refresh records healthy snapshot", async () => {
109112
expect(autoRegisterCalls.length).toBe(1);
110113
});
111114

115+
test("getProjectsPayload keeps working when resolveProjectMeta fails for one project", async () => {
116+
const createdAt = new Date().toISOString();
117+
118+
const projects: RegisteredProject[] = [
119+
{
120+
id: "ok",
121+
name: "ok",
122+
repoRoot: "/tmp/ok",
123+
projectDirName: HACK_PROJECT_DIR_PRIMARY,
124+
projectDir: "/tmp/ok/.hack",
125+
createdAt,
126+
},
127+
{
128+
id: "bad",
129+
name: "bad",
130+
repoRoot: "/tmp/bad",
131+
projectDirName: HACK_PROJECT_DIR_PRIMARY,
132+
projectDir: "/tmp/bad/.hack",
133+
createdAt,
134+
},
135+
];
136+
137+
const makeView = (name: string): ProjectView => ({
138+
name,
139+
devHost: null,
140+
repoRoot: null,
141+
projectDir: null,
142+
definedServices: null,
143+
extensionsEnabled: null,
144+
features: null,
145+
serviceHosts: null,
146+
runtimeConfigured: null,
147+
runtimeStatus: "unknown",
148+
runtime: null,
149+
branchRuntime: [],
150+
kind: "registered",
151+
status: "unknown",
152+
});
153+
154+
const cache = createRuntimeCache({
155+
deps: {
156+
readProjectsRegistry: async () => ({ version: 1, projects }),
157+
buildProjectViews: async () => [makeView("ok"), makeView("bad")],
158+
serializeProjectView: (view) => ({ name: view.name, kind: view.kind }),
159+
resolveProjectMeta: async (opts) => {
160+
if (opts.projectName === "bad") {
161+
throw new Error("boom");
162+
}
163+
return { projectName: opts.projectName };
164+
},
165+
},
166+
});
167+
168+
await cache.refresh({ reason: "test" });
169+
170+
const payload = await cache.getProjectsPayload({
171+
filter: null,
172+
includeGlobal: true,
173+
includeUnregistered: true,
174+
includeMeta: true,
175+
});
176+
177+
expect(payload.projects.length).toBe(2);
178+
expect(payload.projects[0]).toMatchObject({
179+
name: "ok",
180+
meta: { projectName: "ok" },
181+
});
182+
expect(payload.projects[1]).toMatchObject({
183+
name: "bad",
184+
meta: null,
185+
});
186+
});
187+
112188
test("runtime cache retains last runtime on failure", async () => {
113189
const runtime: RuntimeProject[] = [
114190
{

0 commit comments

Comments
 (0)