Added complete test coverage for the identifier-resolver utility module which provides username/UUID resolution functionality. The test suite includes: - isUUID validation tests (valid/invalid formats, case sensitivity, edge cases) - resolveIdentifierToCreator tests (username lookups, UUID fallbacks, error handling) - resolveIdentifierToUserId tests (optimization paths, resolution logic) - resolveIdentifierToUsername tests (direct returns, API resolution) - Integration scenarios (full workflows, mixed case handling) All 26 tests passing with comprehensive mocking of fetch API calls.
403 lines
12 KiB
TypeScript
403 lines
12 KiB
TypeScript
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
import {
|
|
isUUID,
|
|
resolveIdentifierToCreator,
|
|
resolveIdentifierToUserId,
|
|
resolveIdentifierToUsername,
|
|
} from "./identifier-resolver";
|
|
|
|
describe("identifier-resolver", () => {
|
|
describe("isUUID", () => {
|
|
it("should return true for valid UUIDs", () => {
|
|
expect(isUUID("550e8400-e29b-41d4-a716-446655440000")).toBe(true);
|
|
expect(isUUID("6ba7b810-9dad-11d1-80b4-00c04fd430c8")).toBe(true);
|
|
expect(isUUID("f47ac10b-58cc-4372-a567-0e02b2c3d479")).toBe(true);
|
|
expect(isUUID("123e4567-e89b-12d3-a456-426614174000")).toBe(true);
|
|
});
|
|
|
|
it("should return true for UUIDs in different cases", () => {
|
|
expect(isUUID("550E8400-E29B-41D4-A716-446655440000")).toBe(true);
|
|
expect(isUUID("550e8400-E29B-41d4-A716-446655440000")).toBe(true);
|
|
});
|
|
|
|
it("should return false for invalid UUIDs", () => {
|
|
expect(isUUID("not-a-uuid")).toBe(false);
|
|
expect(isUUID("550e8400-e29b-41d4-a716")).toBe(false); // too short
|
|
expect(isUUID("550e8400-e29b-41d4-a716-446655440000-extra")).toBe(false); // too long
|
|
expect(isUUID("550e8400e29b41d4a716446655440000")).toBe(false); // missing dashes
|
|
expect(isUUID("550e8400-e29b-41d4-a716-44665544000g")).toBe(false); // invalid character 'g'
|
|
});
|
|
|
|
it("should return false for empty and invalid inputs", () => {
|
|
expect(isUUID("")).toBe(false);
|
|
expect(isUUID(" ")).toBe(false);
|
|
});
|
|
|
|
it("should return false for non-string-like UUIDs", () => {
|
|
expect(isUUID("12345678-1234-1234-1234-12345678901")).toBe(false); // one char short
|
|
expect(isUUID("12345678-1234-1234-1234-1234567890123")).toBe(false); // one char long
|
|
});
|
|
});
|
|
|
|
describe("resolveIdentifierToCreator", () => {
|
|
let fetchSpy: any;
|
|
|
|
beforeEach(() => {
|
|
// Mock the global fetch function
|
|
fetchSpy = vi.fn();
|
|
global.fetch = fetchSpy;
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it("should return null for empty identifier", async () => {
|
|
const result = await resolveIdentifierToCreator("");
|
|
expect(result).toBeNull();
|
|
expect(fetchSpy).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("should resolve username successfully", async () => {
|
|
const mockCreator = {
|
|
id: "550e8400-e29b-41d4-a716-446655440000",
|
|
username: "testuser",
|
|
email: "test@example.com",
|
|
};
|
|
|
|
fetchSpy.mockResolvedValueOnce({
|
|
ok: true,
|
|
json: async () => mockCreator,
|
|
});
|
|
|
|
const result = await resolveIdentifierToCreator("testuser");
|
|
|
|
expect(result).toEqual(mockCreator);
|
|
expect(fetchSpy).toHaveBeenCalledTimes(1);
|
|
expect(fetchSpy).toHaveBeenCalledWith("/api/creators/testuser");
|
|
});
|
|
|
|
it("should try UUID lookup when username fails and identifier is UUID", async () => {
|
|
const uuid = "550e8400-e29b-41d4-a716-446655440000";
|
|
const mockCreator = {
|
|
id: uuid,
|
|
username: "testuser",
|
|
email: "test@example.com",
|
|
};
|
|
|
|
// First call (username) fails
|
|
fetchSpy.mockResolvedValueOnce({
|
|
ok: false,
|
|
status: 404,
|
|
});
|
|
|
|
// Second call (UUID) succeeds
|
|
fetchSpy.mockResolvedValueOnce({
|
|
ok: true,
|
|
json: async () => mockCreator,
|
|
});
|
|
|
|
const result = await resolveIdentifierToCreator(uuid);
|
|
|
|
expect(result).toEqual(mockCreator);
|
|
expect(fetchSpy).toHaveBeenCalledTimes(2);
|
|
expect(fetchSpy).toHaveBeenNthCalledWith(1, `/api/creators/${uuid}`);
|
|
expect(fetchSpy).toHaveBeenNthCalledWith(2, `/api/creators/user/${uuid}`);
|
|
});
|
|
|
|
it("should return null when username lookup fails and identifier is not a UUID", async () => {
|
|
fetchSpy.mockResolvedValueOnce({
|
|
ok: false,
|
|
status: 404,
|
|
});
|
|
|
|
const result = await resolveIdentifierToCreator("notauser");
|
|
|
|
expect(result).toBeNull();
|
|
expect(fetchSpy).toHaveBeenCalledTimes(1);
|
|
expect(fetchSpy).toHaveBeenCalledWith("/api/creators/notauser");
|
|
});
|
|
|
|
it("should return null when both username and UUID lookups fail", async () => {
|
|
const uuid = "550e8400-e29b-41d4-a716-446655440000";
|
|
|
|
fetchSpy.mockResolvedValueOnce({
|
|
ok: false,
|
|
status: 404,
|
|
});
|
|
|
|
fetchSpy.mockResolvedValueOnce({
|
|
ok: false,
|
|
status: 404,
|
|
});
|
|
|
|
const result = await resolveIdentifierToCreator(uuid);
|
|
|
|
expect(result).toBeNull();
|
|
expect(fetchSpy).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it("should handle fetch errors gracefully", async () => {
|
|
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
|
|
fetchSpy.mockRejectedValueOnce(new Error("Network error"));
|
|
|
|
const result = await resolveIdentifierToCreator("testuser");
|
|
|
|
expect(result).toBeNull();
|
|
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
"[Identifier Resolver] Error resolving identifier:",
|
|
expect.any(Error)
|
|
);
|
|
|
|
consoleErrorSpy.mockRestore();
|
|
});
|
|
|
|
it("should handle malformed JSON responses", async () => {
|
|
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
|
|
fetchSpy.mockResolvedValueOnce({
|
|
ok: true,
|
|
json: async () => {
|
|
throw new Error("Invalid JSON");
|
|
},
|
|
});
|
|
|
|
const result = await resolveIdentifierToCreator("testuser");
|
|
|
|
expect(result).toBeNull();
|
|
expect(consoleErrorSpy).toHaveBeenCalled();
|
|
|
|
consoleErrorSpy.mockRestore();
|
|
});
|
|
});
|
|
|
|
describe("resolveIdentifierToUserId", () => {
|
|
let fetchSpy: any;
|
|
|
|
beforeEach(() => {
|
|
fetchSpy = vi.fn();
|
|
global.fetch = fetchSpy;
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it("should return null for empty identifier", async () => {
|
|
const result = await resolveIdentifierToUserId("");
|
|
expect(result).toBeNull();
|
|
expect(fetchSpy).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("should return the UUID directly if identifier is already a UUID", async () => {
|
|
const uuid = "550e8400-e29b-41d4-a716-446655440000";
|
|
const result = await resolveIdentifierToUserId(uuid);
|
|
|
|
expect(result).toBe(uuid);
|
|
expect(fetchSpy).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("should resolve username to user ID", async () => {
|
|
const mockCreator = {
|
|
id: "550e8400-e29b-41d4-a716-446655440000",
|
|
username: "testuser",
|
|
};
|
|
|
|
fetchSpy.mockResolvedValueOnce({
|
|
ok: true,
|
|
json: async () => mockCreator,
|
|
});
|
|
|
|
const result = await resolveIdentifierToUserId("testuser");
|
|
|
|
expect(result).toBe(mockCreator.id);
|
|
expect(fetchSpy).toHaveBeenCalledWith("/api/creators/testuser");
|
|
});
|
|
|
|
it("should return null when username resolution fails", async () => {
|
|
fetchSpy.mockResolvedValueOnce({
|
|
ok: false,
|
|
status: 404,
|
|
});
|
|
|
|
const result = await resolveIdentifierToUserId("nonexistent");
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it("should return null when API returns creator without ID", async () => {
|
|
fetchSpy.mockResolvedValueOnce({
|
|
ok: true,
|
|
json: async () => ({ username: "testuser" }), // no id field
|
|
});
|
|
|
|
const result = await resolveIdentifierToUserId("testuser");
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it("should handle errors gracefully", async () => {
|
|
fetchSpy.mockRejectedValueOnce(new Error("Network error"));
|
|
|
|
const result = await resolveIdentifierToUserId("testuser");
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("resolveIdentifierToUsername", () => {
|
|
let fetchSpy: any;
|
|
|
|
beforeEach(() => {
|
|
fetchSpy = vi.fn();
|
|
global.fetch = fetchSpy;
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it("should return null for empty identifier", async () => {
|
|
const result = await resolveIdentifierToUsername("");
|
|
expect(result).toBeNull();
|
|
expect(fetchSpy).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("should return the username directly if identifier is not a UUID", async () => {
|
|
const result = await resolveIdentifierToUsername("testuser");
|
|
|
|
expect(result).toBe("testuser");
|
|
expect(fetchSpy).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("should resolve UUID to username", async () => {
|
|
const uuid = "550e8400-e29b-41d4-a716-446655440000";
|
|
const mockCreator = {
|
|
id: uuid,
|
|
username: "testuser",
|
|
};
|
|
|
|
// First call (username endpoint with UUID) will fail
|
|
fetchSpy.mockResolvedValueOnce({
|
|
ok: false,
|
|
status: 404,
|
|
});
|
|
|
|
// Second call (UUID endpoint) will succeed
|
|
fetchSpy.mockResolvedValueOnce({
|
|
ok: true,
|
|
json: async () => mockCreator,
|
|
});
|
|
|
|
const result = await resolveIdentifierToUsername(uuid);
|
|
|
|
expect(result).toBe(mockCreator.username);
|
|
expect(fetchSpy).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it("should return null when UUID resolution fails", async () => {
|
|
const uuid = "550e8400-e29b-41d4-a716-446655440000";
|
|
|
|
fetchSpy.mockResolvedValueOnce({
|
|
ok: false,
|
|
status: 404,
|
|
});
|
|
|
|
fetchSpy.mockResolvedValueOnce({
|
|
ok: false,
|
|
status: 404,
|
|
});
|
|
|
|
const result = await resolveIdentifierToUsername(uuid);
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it("should return null when API returns creator without username", async () => {
|
|
const uuid = "550e8400-e29b-41d4-a716-446655440000";
|
|
|
|
fetchSpy.mockResolvedValueOnce({
|
|
ok: false,
|
|
status: 404,
|
|
});
|
|
|
|
fetchSpy.mockResolvedValueOnce({
|
|
ok: true,
|
|
json: async () => ({ id: uuid }), // no username field
|
|
});
|
|
|
|
const result = await resolveIdentifierToUsername(uuid);
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it("should handle errors gracefully", async () => {
|
|
const uuid = "550e8400-e29b-41d4-a716-446655440000";
|
|
fetchSpy.mockRejectedValueOnce(new Error("Network error"));
|
|
|
|
const result = await resolveIdentifierToUsername(uuid);
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("integration scenarios", () => {
|
|
let fetchSpy: any;
|
|
|
|
beforeEach(() => {
|
|
fetchSpy = vi.fn();
|
|
global.fetch = fetchSpy;
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it("should handle full workflow: username -> userId -> username", async () => {
|
|
const mockCreator = {
|
|
id: "550e8400-e29b-41d4-a716-446655440000",
|
|
username: "testuser",
|
|
};
|
|
|
|
// First call: resolve username to creator
|
|
fetchSpy.mockResolvedValueOnce({
|
|
ok: true,
|
|
json: async () => mockCreator,
|
|
});
|
|
|
|
const userId = await resolveIdentifierToUserId("testuser");
|
|
expect(userId).toBe(mockCreator.id);
|
|
|
|
// Second call: resolve userId (UUID) back to username
|
|
// This will make API calls since userId is a UUID
|
|
fetchSpy.mockResolvedValueOnce({
|
|
ok: false, // username endpoint will fail
|
|
status: 404,
|
|
});
|
|
|
|
fetchSpy.mockResolvedValueOnce({
|
|
ok: true,
|
|
json: async () => mockCreator,
|
|
});
|
|
|
|
const username = await resolveIdentifierToUsername(userId);
|
|
expect(username).toBe(mockCreator.username);
|
|
});
|
|
|
|
it("should handle mixed case UUIDs consistently", async () => {
|
|
const lowerUUID = "550e8400-e29b-41d4-a716-446655440000";
|
|
const upperUUID = "550E8400-E29B-41D4-A716-446655440000";
|
|
const mixedUUID = "550e8400-E29B-41d4-A716-446655440000";
|
|
|
|
expect(isUUID(lowerUUID)).toBe(true);
|
|
expect(isUUID(upperUUID)).toBe(true);
|
|
expect(isUUID(mixedUUID)).toBe(true);
|
|
|
|
// All should be recognized as UUIDs and returned directly
|
|
expect(await resolveIdentifierToUserId(lowerUUID)).toBe(lowerUUID);
|
|
expect(await resolveIdentifierToUserId(upperUUID)).toBe(upperUUID);
|
|
expect(await resolveIdentifierToUserId(mixedUUID)).toBe(mixedUUID);
|
|
});
|
|
});
|
|
});
|