From acd44b57bb1475e29b1d02224c3da2dbcddea855 Mon Sep 17 00:00:00 2001 From: Karl Ludwig Weise Date: Fri, 14 Aug 2026 17:09:23 +0200 Subject: [PATCH] Fix lint, check and test --- .../__tests__/appointment-service.test.ts | 2 +- .../__tests__/schedule-service.test.ts | 32 ++++++------- src/lib/server/services/schedule-service.ts | 2 +- src/lib/utils/passkey.ts | 2 - .../setup-passkey/setup-passkey-form.svelte | 1 - .../add-passkey-form/add-passkey-form.svelte | 1 - .../calendar/__tests__/calendar-api.test.ts | 18 +++++--- .../key-shard/__tests__/key-shard-api.test.ts | 45 ++++++++++++------- 8 files changed, 60 insertions(+), 43 deletions(-) diff --git a/src/lib/server/services/__tests__/appointment-service.test.ts b/src/lib/server/services/__tests__/appointment-service.test.ts index 3fb3066..e06339d 100644 --- a/src/lib/server/services/__tests__/appointment-service.test.ts +++ b/src/lib/server/services/__tests__/appointment-service.test.ts @@ -38,7 +38,7 @@ vi.mock("../notification-service", () => ({ vi.mock("../../auth/webauthn-service", () => ({ WebAuthnService: { // getClientTunnels scopes staff key shares to the caller's most recently used passkey. - getMostRecentPasskey: vi.fn().mockResolvedValue({ id: "passkey-123", lastUsedAt: new Date() }), + getCurrentPasskey: vi.fn().mockResolvedValue({ id: "passkey-123", lastUsedAt: new Date() }), }, })); diff --git a/src/lib/server/services/__tests__/schedule-service.test.ts b/src/lib/server/services/__tests__/schedule-service.test.ts index 9858154..cbe80b3 100644 --- a/src/lib/server/services/__tests__/schedule-service.test.ts +++ b/src/lib/server/services/__tests__/schedule-service.test.ts @@ -23,7 +23,7 @@ vi.mock("$lib/logger", () => ({ vi.mock("../../auth/webauthn-service", () => ({ WebAuthnService: { // Staff key shares are scoped to the caller's most recently used passkey. - getMostRecentPasskey: vi.fn().mockResolvedValue({ id: "passkey-123", lastUsedAt: new Date() }), + getCurrentPasskey: vi.fn().mockResolvedValue({ id: "passkey-123", lastUsedAt: new Date() }), }, })); @@ -146,9 +146,9 @@ describe("ScheduleService", () => { tenantId: "invalid-uuid", }; - await expect(service.getSchedule(invalidRequest as ScheduleRequest)).rejects.toThrow( - ValidationError, - ); + await expect( + service.getSchedule(invalidRequest as ScheduleRequest, "passkeyId"), + ).rejects.toThrow(ValidationError); }); it("should generate schedule for valid date range", async () => { @@ -206,7 +206,7 @@ describe("ScheduleService", () => { channelAgents: mockChannelAgents, }); - const result = await service.getSchedule(validRequest); + const result = await service.getSchedule(validRequest, "passkeyId"); // Validate basic structure expect(result).toHaveProperty("period"); @@ -265,7 +265,7 @@ describe("ScheduleService", () => { channelAgents: [], }); - const result = await service.getSchedule(validRequest); + const result = await service.getSchedule(validRequest, "passkeyId"); expect(result.schedule).toHaveLength(1); // One day expect(result.schedule[0].channels).toEqual({}); @@ -283,7 +283,9 @@ describe("ScheduleService", () => { throw new Error("Database error"); }); - await expect(service.getSchedule(validRequest)).rejects.toThrow("Database error"); + await expect(service.getSchedule(validRequest, "passkeyId")).rejects.toThrow( + "Database error", + ); }); it("should generate multiple days for date range", async () => { @@ -303,7 +305,7 @@ describe("ScheduleService", () => { channelAgents: [], }); - const result = await service.getSchedule(validRequest); + const result = await service.getSchedule(validRequest, "passkeyId"); expect(result.schedule).toHaveLength(3); // Three days expect(result.schedule[0].date).toBe("2024-01-01"); @@ -384,7 +386,7 @@ describe("ScheduleService", () => { channelAgents: mockChannelAgents, }); - const result = await service.getSchedule(validRequest); + const result = await service.getSchedule(validRequest, "passkeyId"); // Should only have Monday slot (09:00-10:00), not Tuesday slot const channelSchedule = result.schedule[0].channels["channel1"]; @@ -459,7 +461,7 @@ describe("ScheduleService", () => { channelAgents: mockChannelAgents, }); - const result = await service.getSchedule(validRequest); + const result = await service.getSchedule(validRequest, "passkeyId"); const channelSchedule = result.schedule[0].channels["channel1"]; // Should only have 10:00-11:00 slot, not 09:00-10:00 (has appointment) @@ -544,7 +546,7 @@ describe("ScheduleService", () => { channelAgents: mockChannelAgents, }); - const result = await service.getSchedule(validRequest); + const result = await service.getSchedule(validRequest, "passkeyId"); // Validate appointments are returned const channelSchedule = result.schedule[0].channels["channel1"]; @@ -642,7 +644,7 @@ describe("ScheduleService", () => { channelAgents: mockChannelAgents, }); - const result = await service.getSchedule(validRequest); + const result = await service.getSchedule(validRequest, "passkeyId"); const channelSchedule = result.schedule[0].channels["channel1"]; // Should have no available slots since only agent is absent @@ -724,7 +726,7 @@ describe("ScheduleService", () => { channelAgents: mockChannelAgents, }); - const result = await service.getSchedule(validRequest); + const result = await service.getSchedule(validRequest, "passkeyId"); const channelSchedule = result.schedule[0].channels["channel1"]; expect(channelSchedule.availableSlots).toHaveLength(1); @@ -828,7 +830,7 @@ describe("ScheduleService", () => { channelAgents: mockChannelAgents, }); - const result = await service.getSchedule(validRequest); + const result = await service.getSchedule(validRequest, "passkeyId"); const channel1Schedule = result.schedule[0].channels["channel1"]; const channel2Schedule = result.schedule[0].channels["channel2"]; @@ -910,7 +912,7 @@ describe("ScheduleService", () => { channelAgents: mockChannelAgents, }); - const result = await service.getSchedule(validRequest); + const result = await service.getSchedule(validRequest, "passkeyId"); const channelSchedule = result.schedule[0].channels["channel1"]; expect(channelSchedule.availableSlots).toHaveLength(2); diff --git a/src/lib/server/services/schedule-service.ts b/src/lib/server/services/schedule-service.ts index 1cb2e49..5e52af4 100644 --- a/src/lib/server/services/schedule-service.ts +++ b/src/lib/server/services/schedule-service.ts @@ -95,7 +95,7 @@ export class ScheduleService { */ async getSchedule( request: ScheduleRequest, - passkeyId: string | undefined, + passkeyId?: string | undefined, ): Promise { const log = logger.setContext("ScheduleService"); diff --git a/src/lib/utils/passkey.ts b/src/lib/utils/passkey.ts index 275df28..63acd1a 100644 --- a/src/lib/utils/passkey.ts +++ b/src/lib/utils/passkey.ts @@ -358,12 +358,10 @@ export const getPasskeyFormData = async ({ email, userId, setPasskeyFieldState, - isAdditionalPasskey, }: { email: string; userId: string; setPasskeyFieldState: (newState: PasskeyState) => void; - isAdditionalPasskey: boolean; }) => { setPasskeyFieldState("loading"); diff --git a/src/routes/(pages)/confirm/setup-passkey/setup-passkey-form.svelte b/src/routes/(pages)/confirm/setup-passkey/setup-passkey-form.svelte index cdd86c6..dd14531 100644 --- a/src/routes/(pages)/confirm/setup-passkey/setup-passkey-form.svelte +++ b/src/routes/(pages)/confirm/setup-passkey/setup-passkey-form.svelte @@ -71,7 +71,6 @@ email: $formData.email, userId: $formData.userId, setPasskeyFieldState: (v) => ($passkeyLoading = v), - isAdditionalPasskey: false, }); if (!data) { diff --git a/src/routes/(pages)/dashboard/account/passkeys/(components)/add-passkey-form/add-passkey-form.svelte b/src/routes/(pages)/dashboard/account/passkeys/(components)/add-passkey-form/add-passkey-form.svelte index 0abd08a..7ee624c 100644 --- a/src/routes/(pages)/dashboard/account/passkeys/(components)/add-passkey-form/add-passkey-form.svelte +++ b/src/routes/(pages)/dashboard/account/passkeys/(components)/add-passkey-form/add-passkey-form.svelte @@ -63,7 +63,6 @@ email: $formData.email, userId: $formData.userId, setPasskeyFieldState: (v) => ($passkeyLoading = v), - isAdditionalPasskey: true, }); if (!data) { diff --git a/src/routes/api/tenants/[id]/calendar/__tests__/calendar-api.test.ts b/src/routes/api/tenants/[id]/calendar/__tests__/calendar-api.test.ts index 8b7d48d..5a26460 100644 --- a/src/routes/api/tenants/[id]/calendar/__tests__/calendar-api.test.ts +++ b/src/routes/api/tenants/[id]/calendar/__tests__/calendar-api.test.ts @@ -54,6 +54,7 @@ describe("Calendar API", () => { locals: { user: { id: "user-123", + passkeyId: "passkey-123", tenantId, role: "GUEST", }, @@ -150,13 +151,16 @@ describe("Calendar API", () => { }); expect(ScheduleService.forTenant).toHaveBeenCalledWith("tenant-123"); - expect(mockGetSchedule).toHaveBeenCalledWith({ - tenantId: "tenant-123", - startDate: "2024-01-01T00:00:00.000Z", - endDate: "2024-01-02T00:00:00.000Z", - timeZone: "UTC", - staffUserId: "user-123", - }); + expect(mockGetSchedule).toHaveBeenCalledWith( + { + tenantId: "tenant-123", + startDate: "2024-01-01T00:00:00.000Z", + endDate: "2024-01-02T00:00:00.000Z", + timeZone: "UTC", + staffUserId: "user-123", + }, + "passkey-123", + ); }); it("should include full appointment and agent information in calendar response", async () => { diff --git a/src/routes/api/tenants/[id]/staff/[staffId]/key-shard/__tests__/key-shard-api.test.ts b/src/routes/api/tenants/[id]/staff/[staffId]/key-shard/__tests__/key-shard-api.test.ts index 79fa5be..9d019ae 100644 --- a/src/routes/api/tenants/[id]/staff/[staffId]/key-shard/__tests__/key-shard-api.test.ts +++ b/src/routes/api/tenants/[id]/staff/[staffId]/key-shard/__tests__/key-shard-api.test.ts @@ -37,7 +37,7 @@ vi.mock("$lib/server/services/staff-crypto.service", () => ({ vi.mock("$lib/server/auth/webauthn-service", () => ({ WebAuthnService: { - getMostRecentPasskey: vi.fn(async () => undefined), + getCurrentPasskey: vi.fn(async () => undefined), }, })); @@ -108,6 +108,7 @@ describe("Staff Key Shard API Route", () => { locals: { user: { id: mockUserId, + passkeyId: mockPasskeyId, session: { sessionId: "session-123", }, @@ -130,7 +131,7 @@ describe("Staff Key Shard API Route", () => { passkeyId: mockPasskeyId, }; - (WebAuthnService.getMostRecentPasskey as any).mockResolvedValue(mockRecentPasskey); + (WebAuthnService.getCurrentPasskey as any).mockResolvedValue(mockRecentPasskey); mockStaffCryptoService.getStaffCryptoForPasskey.mockResolvedValue(mockStaffCrypto); const event = createMockRequestEvent(); @@ -144,7 +145,7 @@ describe("Staff Key Shard API Route", () => { passkeyId: mockStaffCrypto.passkeyId, }); - expect(WebAuthnService.getMostRecentPasskey).toHaveBeenCalledWith(mockUserId); + expect(WebAuthnService.getCurrentPasskey).toHaveBeenCalledWith(mockUserId, mockPasskeyId); expect(mockStaffCryptoService.getStaffCryptoForPasskey).toHaveBeenCalledWith( mockTenantId, mockStaffId, @@ -157,6 +158,7 @@ describe("Staff Key Shard API Route", () => { locals: { user: { id: "different-user-id", + passkeyId: mockPasskeyId, session: { sessionId: "session-123" }, }, } as any, @@ -168,7 +170,7 @@ describe("Staff Key Shard API Route", () => { expect(response.status).toBe(403); expect(data.error).toBe("You can only access your own key shard"); - expect(WebAuthnService.getMostRecentPasskey).not.toHaveBeenCalled(); + expect(WebAuthnService.getCurrentPasskey).not.toHaveBeenCalled(); expect(mockStaffCryptoService.getStaffCryptoForPasskey).not.toHaveBeenCalled(); }); @@ -187,7 +189,7 @@ describe("Staff Key Shard API Route", () => { }); it("should reject access when no recent passkey is found", async () => { - (WebAuthnService.getMostRecentPasskey as any).mockResolvedValue(null); + (WebAuthnService.getCurrentPasskey as any).mockResolvedValue(null); const event = createMockRequestEvent(); const response = await GET(event); @@ -196,7 +198,7 @@ describe("Staff Key Shard API Route", () => { expect(response.status).toBe(403); expect(data.error).toBe("No valid passkey found for authenticated user"); - expect(WebAuthnService.getMostRecentPasskey).toHaveBeenCalledWith(mockUserId); + expect(WebAuthnService.getCurrentPasskey).toHaveBeenCalledWith(mockUserId, mockPasskeyId); expect(mockStaffCryptoService.getStaffCryptoForPasskey).not.toHaveBeenCalled(); }); @@ -206,7 +208,7 @@ describe("Staff Key Shard API Route", () => { lastUsedAt: new Date("2024-01-15T10:00:00Z"), }; - (WebAuthnService.getMostRecentPasskey as any).mockResolvedValue(mockRecentPasskey); + (WebAuthnService.getCurrentPasskey as any).mockResolvedValue(mockRecentPasskey); mockStaffCryptoService.getStaffCryptoForPasskey.mockResolvedValue(null); const event = createMockRequestEvent(); @@ -216,7 +218,7 @@ describe("Staff Key Shard API Route", () => { expect(response.status).toBe(404); expect(data.error).toBe("Staff crypto data not found for the authenticated passkey"); - expect(WebAuthnService.getMostRecentPasskey).toHaveBeenCalledWith(mockUserId); + expect(WebAuthnService.getCurrentPasskey).toHaveBeenCalledWith(mockUserId, mockPasskeyId); expect(mockStaffCryptoService.getStaffCryptoForPasskey).toHaveBeenCalledWith( mockTenantId, mockStaffId, @@ -249,7 +251,7 @@ describe("Staff Key Shard API Route", () => { }); it("should handle WebAuthn service errors gracefully", async () => { - (WebAuthnService.getMostRecentPasskey as any).mockRejectedValue( + (WebAuthnService.getCurrentPasskey as any).mockRejectedValue( new Error("Database connection failed"), ); @@ -260,7 +262,7 @@ describe("Staff Key Shard API Route", () => { expect(response.status).toBe(500); expect(data.error).toBe("Failed to determine passkey ID"); - expect(WebAuthnService.getMostRecentPasskey).toHaveBeenCalledWith(mockUserId); + expect(WebAuthnService.getCurrentPasskey).toHaveBeenCalledWith(mockUserId, mockPasskeyId); expect(mockStaffCryptoService.getStaffCryptoForPasskey).not.toHaveBeenCalled(); }); @@ -270,7 +272,7 @@ describe("Staff Key Shard API Route", () => { lastUsedAt: new Date("2024-01-15T10:00:00Z"), }; - (WebAuthnService.getMostRecentPasskey as any).mockResolvedValue(mockRecentPasskey); + (WebAuthnService.getCurrentPasskey as any).mockResolvedValue(mockRecentPasskey); mockStaffCryptoService.getStaffCryptoForPasskey.mockRejectedValue( new Error("Database connection failed"), ); @@ -282,7 +284,7 @@ describe("Staff Key Shard API Route", () => { expect(response.status).toBe(500); expect(data.error).toBe("Internal server error"); - expect(WebAuthnService.getMostRecentPasskey).toHaveBeenCalledWith(mockUserId); + expect(WebAuthnService.getCurrentPasskey).toHaveBeenCalledWith(mockUserId, mockPasskeyId); expect(mockStaffCryptoService.getStaffCryptoForPasskey).toHaveBeenCalledWith( mockTenantId, mockStaffId, @@ -302,7 +304,7 @@ describe("Staff Key Shard API Route", () => { passkeyId: mockPasskeyId, }; - (WebAuthnService.getMostRecentPasskey as any).mockResolvedValue(mockRecentPasskey); + (WebAuthnService.getCurrentPasskey as any).mockResolvedValue(mockRecentPasskey); mockStaffCryptoService.getStaffCryptoForPasskey.mockResolvedValue(mockStaffCrypto); const event = createMockRequestEvent(); @@ -352,11 +354,24 @@ describe("Staff Key Shard API Route", () => { locals: { user: { id: testCase.requestingUserId, + passkeyId: mockPasskeyId, session: { sessionId: "session-123" }, }, } as any, }); + if (testCase.shouldAllow) { + (WebAuthnService.getCurrentPasskey as any).mockResolvedValue({ + id: mockPasskeyId, + lastUsedAt: new Date("2024-01-15T10:00:00Z"), + }); + mockStaffCryptoService.getStaffCryptoForPasskey.mockResolvedValue({ + publicKey: "base64-encoded-ml-kem-768-public-key==", + privateKeyShare: "base64-encoded-private-key-shard==", + passkeyId: mockPasskeyId, + }); + } + const response = await GET(event); if (testCase.shouldAllow) { @@ -371,7 +386,7 @@ describe("Staff Key Shard API Route", () => { it("should require valid passkey authentication", async () => { // Mock no recent passkey (security violation) - (WebAuthnService.getMostRecentPasskey as any).mockResolvedValue(null); + (WebAuthnService.getCurrentPasskey as any).mockResolvedValue(null); const event = createMockRequestEvent(); const response = await GET(event); @@ -387,7 +402,7 @@ describe("Staff Key Shard API Route", () => { lastUsedAt: new Date("2024-01-15T10:00:00Z"), }; - (WebAuthnService.getMostRecentPasskey as any).mockResolvedValue(mockRecentPasskey); + (WebAuthnService.getCurrentPasskey as any).mockResolvedValue(mockRecentPasskey); // Mock no crypto data for this passkey (security violation) mockStaffCryptoService.getStaffCryptoForPasskey.mockResolvedValue(null);