test: expand test coverage across unit, integration, e2e, and e2e-docker suites

Add ~210 new tests filling gaps identified by a comprehensive 14-agent
coverage audit. Unit+integration tests go from 9,388 to 9,484 (all passing).

Unit tests (+36):
- AI bridge: OOM fallback path, custom tier option
- Web lib: api-errors, format date/datetime, tool-i18n coverage

Integration tests (+19):
- Format matrix: ai-canvas-expand and find-duplicates added to cross-format matrix
- Adversarial: SVG XXE attacks, SQL injection in settings, request body size
  limits, race conditions with identical filenames

E2E Docker (+3):
- ai-canvas-expand tool coverage with HEIC input and edge cases

E2E GUI (~150+):
- Navigation: login rate limiting, ai-canvas-expand in parameterized list
- Responsive: dropzone visibility, text readability, dialog bounds at all viewports
- Keyboard: shortcuts verified from automate, files, tool, and fullscreen pages
- Tool UI: undo/state-reset for 16 tools, crop canvas drag handles, rotate/border
  live preview, linked aspect-ratio inputs for resize
- Batch: per-image undo isolation, batch compress/convert/rotate (not just resize)
- Pipeline: tool palette search, step collapse/expand visibility
- Settings: audit log entry verification, system settings persistence, teams CRUD,
  role permission toggling
- RBAC: user/editor 403 on roles/teams endpoints, privilege escalation prevention,
  cross-role tab parity documented as intentional
- Accessibility: skip-to-content link (WCAG 2.4.1), comprehensive color contrast
  for all headings/body/buttons in both themes with DOM-walking background detection
- Resilience: auth expiry 401 redirect, rate limit 429 handling
- Performance: JS heap memory stability for tool navigation, dialog cycling,
  upload/clear cycles, rapid page navigation
This commit is contained in:
SnapOtter
2026-05-15 21:35:02 +08:00
parent d38621d7b9
commit 3b181dd1ac
24 changed files with 3398 additions and 0 deletions
+32
View File
@@ -472,6 +472,38 @@ describe("removeBackground", () => {
expect(runPythonWithProgress).toHaveBeenCalledTimes(2);
});
it("throws when OOM fallback succeeds at bridge level but Python returns success: false", async () => {
vi.mocked(runPythonWithProgress)
.mockRejectedValueOnce(new Error("Process killed (out of memory)"))
.mockResolvedValueOnce({
stdout: '{"success": false, "error": "u2net model corrupt"}',
stderr: "",
});
// Only one parseStdoutJson call happens: the first attempt rejects before parsing
vi.mocked(parseStdoutJson).mockReturnValueOnce({
success: false,
error: "u2net model corrupt",
});
await expect(
removeBackground(FAKE_INPUT, FAKE_OUTPUT_DIR, { model: "birefnet-general" }),
).rejects.toThrow("u2net model corrupt");
expect(runPythonWithProgress).toHaveBeenCalledTimes(2);
});
it("uses fallback error message when OOM fallback returns success: false without error", async () => {
vi.mocked(runPythonWithProgress)
.mockRejectedValueOnce(new Error("Process killed (out of memory)"))
.mockResolvedValueOnce({ stdout: '{"success": false}', stderr: "" });
// Only one parseStdoutJson call happens: the first attempt rejects before parsing
vi.mocked(parseStdoutJson).mockReturnValueOnce({ success: false });
await expect(
removeBackground(FAKE_INPUT, FAKE_OUTPUT_DIR, { model: "birefnet-general" }),
).rejects.toThrow("Background removal failed");
});
it("retries with fallback when no model is specified (default)", async () => {
vi.mocked(runPythonWithProgress)
.mockRejectedValueOnce(new Error("Process killed (out of memory)"))
+39
View File
@@ -93,6 +93,45 @@ describe("outpaint", () => {
);
});
it("passes custom tier value instead of default 'balanced'", async () => {
const options: OutpaintOptions = {
extendTop: 10,
extendRight: 20,
extendBottom: 30,
extendLeft: 40,
tier: "high",
};
await outpaint(FAKE_INPUT, options, FAKE_OUTPUT_DIR);
expect(runPythonWithProgress).toHaveBeenCalledWith(
"outpaint.py",
[
`${FAKE_OUTPUT_DIR}/input_outpaint.png`,
`${FAKE_OUTPUT_DIR}/output_outpaint.png`,
"10",
"20",
"30",
"40",
"high",
],
expect.any(Object),
);
});
it("passes 'fast' tier value", async () => {
const options: OutpaintOptions = {
extendTop: 5,
extendRight: 5,
extendBottom: 5,
extendLeft: 5,
tier: "fast",
};
await outpaint(FAKE_INPUT, options, FAKE_OUTPUT_DIR);
const args = vi.mocked(runPythonWithProgress).mock.calls[0][1];
expect(args[6]).toBe("fast");
});
it("converts input to PNG via sharp", async () => {
const options: OutpaintOptions = {
extendTop: 0,