Add comprehensive tests for identifier-resolver module
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.
This commit is contained in:
parent
7e1e41bb02
commit
1d83347992
1 changed files with 403 additions and 0 deletions
403
client/lib/identifier-resolver.spec.ts
Normal file
403
client/lib/identifier-resolver.spec.ts
Normal file
|
|
@ -0,0 +1,403 @@
|
|||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue