/** * Identity Resolution Service Tests * * Tests for resolving identities between local and remote instances. */ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; import { Test, TestingModule } from "@nestjs/testing"; import { IdentityResolutionService } from "./identity-resolution.service"; import { IdentityLinkingService } from "./identity-linking.service"; import type { FederatedIdentity } from "./types/oidc.types"; describe("IdentityResolutionService", () => { let service: IdentityResolutionService; let identityLinkingService: IdentityLinkingService; const mockIdentity: FederatedIdentity = { id: "identity-id", localUserId: "local-user-id", remoteUserId: "remote-user-id", remoteInstanceId: "remote-instance-id", oidcSubject: "oidc-subject", email: "user@example.com", metadata: {}, createdAt: new Date(), updatedAt: new Date(), }; beforeEach(async () => { const mockIdentityLinkingService = { resolveLocalIdentity: vi.fn(), resolveRemoteIdentity: vi.fn(), }; const module: TestingModule = await Test.createTestingModule({ providers: [ IdentityResolutionService, { provide: IdentityLinkingService, useValue: mockIdentityLinkingService }, ], }).compile(); service = module.get(IdentityResolutionService); identityLinkingService = module.get(IdentityLinkingService); }); afterEach(() => { vi.clearAllMocks(); }); describe("resolveIdentity", () => { it("should resolve remote identity to local user", async () => { identityLinkingService.resolveLocalIdentity.mockResolvedValue(mockIdentity); const result = await service.resolveIdentity("remote-instance-id", "remote-user-id"); expect(result.found).toBe(true); expect(result.localUserId).toBe("local-user-id"); expect(result.remoteUserId).toBe("remote-user-id"); expect(result.email).toBe("user@example.com"); expect(identityLinkingService.resolveLocalIdentity).toHaveBeenCalledWith( "remote-instance-id", "remote-user-id" ); }); it("should return not found when mapping does not exist", async () => { identityLinkingService.resolveLocalIdentity.mockResolvedValue(null); const result = await service.resolveIdentity("remote-instance-id", "unknown-user-id"); expect(result.found).toBe(false); expect(result.localUserId).toBeUndefined(); expect(result.remoteUserId).toBe("unknown-user-id"); }); }); describe("reverseResolveIdentity", () => { it("should resolve local user to remote identity", async () => { identityLinkingService.resolveRemoteIdentity.mockResolvedValue(mockIdentity); const result = await service.reverseResolveIdentity("local-user-id", "remote-instance-id"); expect(result.found).toBe(true); expect(result.remoteUserId).toBe("remote-user-id"); expect(result.localUserId).toBe("local-user-id"); expect(identityLinkingService.resolveRemoteIdentity).toHaveBeenCalledWith( "local-user-id", "remote-instance-id" ); }); it("should return not found when mapping does not exist", async () => { identityLinkingService.resolveRemoteIdentity.mockResolvedValue(null); const result = await service.reverseResolveIdentity("unknown-user-id", "remote-instance-id"); expect(result.found).toBe(false); expect(result.remoteUserId).toBeUndefined(); expect(result.localUserId).toBe("unknown-user-id"); }); }); describe("bulkResolveIdentities", () => { it("should resolve multiple remote users to local users", async () => { const mockIdentity2: FederatedIdentity = { ...mockIdentity, id: "identity-id-2", localUserId: "local-user-id-2", remoteUserId: "remote-user-id-2", }; identityLinkingService.resolveLocalIdentity .mockResolvedValueOnce(mockIdentity) .mockResolvedValueOnce(mockIdentity2) .mockResolvedValueOnce(null); const result = await service.bulkResolveIdentities("remote-instance-id", [ "remote-user-id", "remote-user-id-2", "unknown-user-id", ]); expect(result.mappings["remote-user-id"]).toBe("local-user-id"); expect(result.mappings["remote-user-id-2"]).toBe("local-user-id-2"); expect(result.notFound).toEqual(["unknown-user-id"]); expect(identityLinkingService.resolveLocalIdentity).toHaveBeenCalledTimes(3); }); it("should handle empty array", async () => { const result = await service.bulkResolveIdentities("remote-instance-id", []); expect(result.mappings).toEqual({}); expect(result.notFound).toEqual([]); expect(identityLinkingService.resolveLocalIdentity).not.toHaveBeenCalled(); }); it("should handle all not found", async () => { identityLinkingService.resolveLocalIdentity .mockResolvedValueOnce(null) .mockResolvedValueOnce(null); const result = await service.bulkResolveIdentities("remote-instance-id", [ "unknown-1", "unknown-2", ]); expect(result.mappings).toEqual({}); expect(result.notFound).toEqual(["unknown-1", "unknown-2"]); }); }); });