mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
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
87 lines
3.0 KiB
TypeScript
87 lines
3.0 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { translateApiError } from "@/lib/api-errors";
|
|
|
|
// Minimal mock of the relevant portion of TranslationKeys
|
|
function makeTranslations(errors: Record<string, string>) {
|
|
return { errors } as Parameters<typeof translateApiError>[1];
|
|
}
|
|
|
|
describe("translateApiError", () => {
|
|
const t = makeTranslations({
|
|
authRequired: "Please log in",
|
|
invalidCredentials: "Wrong username or password",
|
|
currentPasswordIncorrect: "Current password is wrong",
|
|
noValidFiles: "No files were uploaded",
|
|
fileTooLarge: "File exceeds size limit",
|
|
rateLimitExceeded: "Too many requests",
|
|
processingFailed: "Processing error",
|
|
timeout: "Timed out",
|
|
connectionError: "Cannot connect",
|
|
permissionDenied: "Access denied",
|
|
notFound: "Resource not found",
|
|
});
|
|
|
|
it("translates 'Authentication required'", () => {
|
|
expect(translateApiError("Authentication required", t)).toBe("Please log in");
|
|
});
|
|
|
|
it("translates 'Invalid credentials'", () => {
|
|
expect(translateApiError("Invalid credentials", t)).toBe("Wrong username or password");
|
|
});
|
|
|
|
it("translates 'Invalid username or password' to same key as invalid credentials", () => {
|
|
expect(translateApiError("Invalid username or password", t)).toBe("Wrong username or password");
|
|
});
|
|
|
|
it("translates 'Current password is incorrect'", () => {
|
|
expect(translateApiError("Current password is incorrect", t)).toBe("Current password is wrong");
|
|
});
|
|
|
|
it("translates 'No valid files uploaded'", () => {
|
|
expect(translateApiError("No valid files uploaded", t)).toBe("No files were uploaded");
|
|
});
|
|
|
|
it("translates 'File too large'", () => {
|
|
expect(translateApiError("File too large", t)).toBe("File exceeds size limit");
|
|
});
|
|
|
|
it("translates 'Rate limit exceeded'", () => {
|
|
expect(translateApiError("Rate limit exceeded", t)).toBe("Too many requests");
|
|
});
|
|
|
|
it("translates 'Processing failed'", () => {
|
|
expect(translateApiError("Processing failed", t)).toBe("Processing error");
|
|
});
|
|
|
|
it("translates 'Request timed out'", () => {
|
|
expect(translateApiError("Request timed out", t)).toBe("Timed out");
|
|
});
|
|
|
|
it("translates 'Connection error'", () => {
|
|
expect(translateApiError("Connection error", t)).toBe("Cannot connect");
|
|
});
|
|
|
|
it("translates 'Permission denied'", () => {
|
|
expect(translateApiError("Permission denied", t)).toBe("Access denied");
|
|
});
|
|
|
|
it("translates 'Not found'", () => {
|
|
expect(translateApiError("Not found", t)).toBe("Resource not found");
|
|
});
|
|
|
|
it("returns original message for unmapped API error", () => {
|
|
expect(translateApiError("Something unexpected happened", t)).toBe(
|
|
"Something unexpected happened",
|
|
);
|
|
});
|
|
|
|
it("returns original message when mapped key is missing from translations", () => {
|
|
const sparseT = makeTranslations({});
|
|
expect(translateApiError("Authentication required", sparseT)).toBe("Authentication required");
|
|
});
|
|
|
|
it("returns empty string if API message is empty", () => {
|
|
expect(translateApiError("", t)).toBe("");
|
|
});
|
|
});
|