Secret Scan / scan (push) Successful in 5s
Build App (Preview) / compute-version (pull_request) Successful in 5s
Secret Scan / scan (pull_request) Successful in 4s
Build App (Preview) / create-release (pull_request) Successful in 1s
Build App (Preview) / build-macos (pull_request) Successful in 2m40s
Build App (Preview) / build-windows (pull_request) Successful in 4m59s
Build App (Preview) / build-linux (pull_request) Successful in 6m28s
Build App (Preview) / prune-previews (pull_request) Successful in 1s
A second Opus review of commit 2 found it had introduced real problems of its own rather than just polish gaps: - remove_project's "None or stale" container-id fallback only handled None. A stale id (the documented start-failure race in start_project_container_locked, where the old container is removed and the new one's id isn't persisted until after start_container succeeds) still 404'd on removal — now treated as success by commit 1's own fix — while the real container survived to block every volume removal with a 409 forever, with nothing in the pending-cleanup record ever naming it. Both remove_project and rebuild_project_container now resolve the container via find_existing_container() unconditionally, matching every other container-destroying path in the codebase, and remove_project fails closed (records a leftover rather than silently skipping) if Docker itself can't be reached to check. - remove_project could leave a pending-cleanup record for a project still live in projects.json: if the store's own save failed after the record was written, startup housekeeping would delete that project's container and volumes out from under it on the next launch. The record is now rolled back when the store write fails. - rebuild_project_container (Reset) only surfaced a leftover volume, not a leftover snapshot image — the more serious failure, since the next container is built from that image whenever it exists, silently reviving the exact system layer Reset was asked to discard. ProjectResetOutcome now carries leftover_image too, and the toast's "run docker volume rm" advice is corrected: the new container has already remounted the volume by the time the toast renders, so that command would just hit the same conflict Reset did. Also from the same pass: reworded a couple of log/toast lines that still asserted resources were "still present" when the daemon-unreachable case covered by the same code path can't actually confirm that; fixed a singular/verb mismatch in the leftover toast text; moved an unparseable pending-cleanup record aside instead of re-warning about it forever; and added a debug log when a record's recorded_at can't be parsed, so aging never silently no-ops. Pulled describeLeftovers/leftoverVerb out of ProjectHome.tsx into their own module with unit tests, and added tests for the recorded_at staleness check — the previous commit's equivalent logic had none. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FGjXq6fqtAFHdbhk4f3PfZ
52 lines
2.1 KiB
TypeScript
52 lines
2.1 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { describeLeftovers, leftoverVerb } from "./removalReport";
|
|
import { projectRemovalIsClean } from "../../../lib/types";
|
|
import type { ProjectRemovalReport } from "../../../lib/types";
|
|
|
|
function report(overrides: Partial<ProjectRemovalReport> = {}): ProjectRemovalReport {
|
|
return {
|
|
container: null,
|
|
image: null,
|
|
volumes: [],
|
|
retry_scheduled: false,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe("projectRemovalIsClean", () => {
|
|
it("is true only when nothing survived", () => {
|
|
expect(projectRemovalIsClean(report())).toBe(true);
|
|
expect(projectRemovalIsClean(report({ container: "triple-c-abc" }))).toBe(false);
|
|
expect(projectRemovalIsClean(report({ image: "triple-c-snapshot-abc:latest" }))).toBe(false);
|
|
expect(projectRemovalIsClean(report({ volumes: ["triple-c-home-abc"] }))).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("describeLeftovers", () => {
|
|
it("names each kind of leftover", () => {
|
|
expect(describeLeftovers(report({ container: "triple-c-abc" }))).toBe("its container");
|
|
expect(describeLeftovers(report({ image: "x" }))).toBe("its saved image");
|
|
expect(describeLeftovers(report({ volumes: ["v1"] }))).toBe("a volume");
|
|
expect(describeLeftovers(report({ volumes: ["v1", "v2"] }))).toBe("2 volumes");
|
|
});
|
|
|
|
it("joins multiple kinds together", () => {
|
|
expect(
|
|
describeLeftovers(report({ container: "triple-c-abc", image: "x", volumes: ["v1", "v2"] })),
|
|
).toBe("its container, its saved image, 2 volumes");
|
|
});
|
|
});
|
|
|
|
describe("leftoverVerb", () => {
|
|
it("is singular for exactly one leftover of any kind", () => {
|
|
expect(leftoverVerb(report({ container: "triple-c-abc" }))).toBe("was");
|
|
expect(leftoverVerb(report({ image: "x" }))).toBe("was");
|
|
expect(leftoverVerb(report({ volumes: ["v1"] }))).toBe("was");
|
|
});
|
|
|
|
it("is plural once more than one thing survived, including multiple volumes alone", () => {
|
|
expect(leftoverVerb(report({ container: "triple-c-abc", image: "x" }))).toBe("were");
|
|
expect(leftoverVerb(report({ volumes: ["v1", "v2"] }))).toBe("were");
|
|
});
|
|
});
|