Files
Triple-C/app/src/components/projects/home/removalReport.test.ts
T
shadow-testandClaude Sonnet 5 439ef16f07
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
Fix two new bugs a second review found: stale container id, orphaned record
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
2026-08-27 09:09:46 -07:00

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");
});
});