mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
Expand test coverage across all layers via 14 parallel agents: Unit tests (3,378 total, +534): - First-ever AI sidecar tests (157 tests covering bridge lifecycle, all 12 tool modules) - API route infrastructure (auth, pipeline, batch, settings, teams, roles, audit, api-keys, files, docs) - Lib coverage improvements (audit 7%->95%, worker-pool 33%->100%) - Web store/lib gap fills (features-store, tool-registry) Integration tests (4,403 total, +903): - Expanded 19 tool test files with parameter variations, format edge cases, boundary values - Cross-format matrix: 290 tests covering 14 tools x 17 formats - Adversarial/edge cases: 63 tests for extreme inputs, concurrent requests, corrupted files E2E-Docker (125 new tests): - Expanded 8 spec files + 1 new file covering all 49 tools - Added HEIC/format handling, auth failures, download verification GUI E2E (expanded 28 spec files): - Navigation, responsive layout, keyboard shortcuts - All 51 tool UIs with settings, processing, display modes - Batch/pipeline workflows, settings/RBAC, visual regression - Resilience, accessibility (ARIA, contrast, focus), performance budgets
158 lines
5.1 KiB
TypeScript
158 lines
5.1 KiB
TypeScript
/**
|
|
* Unit tests for teams route validation and helper logic.
|
|
*
|
|
* Tests the team name validation schema, duplicate detection logic,
|
|
* and Default team protection constraints.
|
|
*/
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
// ── Reproduce the validation logic from teams.ts ───────────────────────
|
|
|
|
function validateTeamName(body: unknown): {
|
|
success: boolean;
|
|
name?: string;
|
|
error?: string;
|
|
} {
|
|
if (typeof body !== "object" || body === null) {
|
|
return { success: false, error: "Team name is required" };
|
|
}
|
|
const raw = (body as Record<string, unknown>).name;
|
|
if (typeof raw !== "string") {
|
|
return { success: false, error: "Team name is required" };
|
|
}
|
|
const trimmed = raw.trim();
|
|
if (trimmed.length === 0) {
|
|
return { success: false, error: "Team name is required" };
|
|
}
|
|
if (trimmed.length > 50) {
|
|
return { success: false, error: "Team name must be 50 characters or fewer" };
|
|
}
|
|
return { success: true, name: trimmed };
|
|
}
|
|
|
|
// ── Tests ───────────────────────────────────────────────────────────────
|
|
|
|
describe("teams route logic", () => {
|
|
describe("team name validation", () => {
|
|
it("accepts valid team name", () => {
|
|
const result = validateTeamName({ name: "Engineering" });
|
|
expect(result.success).toBe(true);
|
|
expect(result.name).toBe("Engineering");
|
|
});
|
|
|
|
it("trims whitespace from team name", () => {
|
|
const result = validateTeamName({ name: " Design " });
|
|
expect(result.success).toBe(true);
|
|
expect(result.name).toBe("Design");
|
|
});
|
|
|
|
it("rejects empty string after trimming", () => {
|
|
const result = validateTeamName({ name: " " });
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it("rejects missing name field", () => {
|
|
const result = validateTeamName({});
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it("rejects name longer than 50 characters", () => {
|
|
const result = validateTeamName({ name: "A".repeat(51) });
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it("accepts name exactly 50 characters", () => {
|
|
const result = validateTeamName({ name: "A".repeat(50) });
|
|
expect(result.success).toBe(true);
|
|
});
|
|
|
|
it("accepts single character name", () => {
|
|
const result = validateTeamName({ name: "X" });
|
|
expect(result.success).toBe(true);
|
|
});
|
|
|
|
it("rejects non-string name", () => {
|
|
const result = validateTeamName({ name: 123 });
|
|
expect(result.success).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("Default team protection", () => {
|
|
it("identifies the Default team by name", () => {
|
|
const team = { name: "Default", id: "some-id" };
|
|
expect(team.name === "Default").toBe(true);
|
|
});
|
|
|
|
it("non-Default teams can be deleted", () => {
|
|
const team = { name: "Engineering", id: "eng-id" };
|
|
expect(team.name === "Default").toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("member count check", () => {
|
|
it("blocks deletion when team has members", () => {
|
|
const memberCount = { count: 3 };
|
|
expect(memberCount.count > 0).toBe(true);
|
|
});
|
|
|
|
it("allows deletion when team has no members", () => {
|
|
const memberCount = { count: 0 };
|
|
expect(memberCount.count > 0).toBe(false);
|
|
});
|
|
|
|
it("handles null member count as zero", () => {
|
|
const memberCount: { count: number } | undefined = undefined;
|
|
const count = memberCount?.count ?? 0;
|
|
expect(count > 0).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("case-insensitive duplicate detection", () => {
|
|
it("detects duplicates case-insensitively", () => {
|
|
const existingNames = ["engineering", "design", "marketing"];
|
|
const newName = "Engineering";
|
|
const isDuplicate = existingNames.some(
|
|
(name) => name.toLowerCase() === newName.toLowerCase(),
|
|
);
|
|
expect(isDuplicate).toBe(true);
|
|
});
|
|
|
|
it("allows unique names", () => {
|
|
const existingNames = ["engineering", "design"];
|
|
const newName = "Marketing";
|
|
const isDuplicate = existingNames.some(
|
|
(name) => name.toLowerCase() === newName.toLowerCase(),
|
|
);
|
|
expect(isDuplicate).toBe(false);
|
|
});
|
|
|
|
it("excludes current team when renaming", () => {
|
|
const existingTeams = [
|
|
{ id: "t1", name: "engineering" },
|
|
{ id: "t2", name: "design" },
|
|
];
|
|
const currentId = "t1";
|
|
const newName = "Engineering";
|
|
|
|
const isDuplicate = existingTeams.some(
|
|
(t) => t.id !== currentId && t.name.toLowerCase() === newName.toLowerCase(),
|
|
);
|
|
expect(isDuplicate).toBe(false);
|
|
});
|
|
|
|
it("detects duplicate when renaming to another team name", () => {
|
|
const existingTeams = [
|
|
{ id: "t1", name: "engineering" },
|
|
{ id: "t2", name: "design" },
|
|
];
|
|
const currentId = "t1";
|
|
const newName = "Design";
|
|
|
|
const isDuplicate = existingTeams.some(
|
|
(t) => t.id !== currentId && t.name.toLowerCase() === newName.toLowerCase(),
|
|
);
|
|
expect(isDuplicate).toBe(true);
|
|
});
|
|
});
|
|
});
|