import { ConflictError, NotFoundError, TechnicalError, ForbiddenError, UserClient, } from "../../../src"; import { Response } from "../../../src/lib/client/HttpClient"; const userID = "test-user-1"; const emailID = "test-email-1"; const email = "test-email-1@test"; const credentials = [{ id: "test-credential-1" }]; let userClient: UserClient; beforeEach(() => { userClient = new UserClient("http://test.api", { cookieName: "hanko", localStorageKey: "hanko", timeout: 13000, }); }); describe("UserClient.getInfo()", () => { it("should retrieve user info", async () => { const response = new Response(new XMLHttpRequest()); response.ok = true; response._decodedJSON = { id: userID, verified: true, has_webauthn_credential: true, }; jest.spyOn(userClient.client, "post").mockResolvedValueOnce(response); const getInfoResponse = userClient.getInfo(email); await expect(getInfoResponse).resolves.toBe(response._decodedJSON); expect(userClient.client.post).toHaveBeenCalledWith("/user", { email, }); }); it("should throw error when user not found", async () => { const response = new Response(new XMLHttpRequest()); response.status = 404; jest.spyOn(userClient.client, "post").mockResolvedValue(response); const user = userClient.getInfo(email); await expect(user).rejects.toThrow(NotFoundError); }); it("should throw error when API response is not ok", async () => { const response = new Response(new XMLHttpRequest()); userClient.client.post = jest.fn().mockResolvedValue(response); const user = userClient.getInfo(email); await expect(user).rejects.toThrowError(TechnicalError); }); it("should throw error on API communication failure", async () => { userClient.client.post = jest .fn() .mockRejectedValue(new Error("Test error")); const user = userClient.getInfo(email); await expect(user).rejects.toThrowError("Test error"); }); }); describe("UserClient.getCurrent()", () => { it("should retrieve currently logged in user", async () => { const responseMe = new Response(new XMLHttpRequest()); responseMe.ok = true; responseMe._decodedJSON = { id: userID, }; const responseUser = new Response(new XMLHttpRequest()); responseUser.ok = true; responseUser._decodedJSON = { id: userID, email, webauthn_credentials: credentials, }; jest .spyOn(userClient.client, "get") .mockResolvedValueOnce(responseMe) .mockResolvedValueOnce(responseUser); const user = userClient.getCurrent(); await expect(user).resolves.toBe(responseUser._decodedJSON); expect(userClient.client.get).toHaveBeenNthCalledWith(1, "/me"); expect(userClient.client.get).toHaveBeenNthCalledWith( 2, `/users/${userID}` ); }); it.each` statusMe | statusUsers | error ${400} | ${200} | ${"Technical error"} ${401} | ${200} | ${"Unauthorized error"} ${404} | ${200} | ${"Technical error"} ${200} | ${400} | ${"Technical error"} ${200} | ${401} | ${"Unauthorized error"} ${200} | ${404} | ${"Technical error"} ${200} | ${500} | ${"Technical error"} ${500} | ${200} | ${"Technical error"} `( "should throw error if API returns an error status", async ({ statusMe, statusUsers, error }) => { const responseMe = new Response(new XMLHttpRequest()); responseMe.status = statusMe; responseMe.ok = statusMe >= 200 && statusMe <= 299; const responseUser = new Response(new XMLHttpRequest()); responseUser.status = statusUsers; responseUser.ok = statusUsers >= 200 && statusUsers <= 299; jest .spyOn(userClient.client, "get") .mockResolvedValueOnce(responseMe) .mockResolvedValueOnce(responseUser); const user = userClient.getCurrent(); await expect(user).rejects.toThrow(error); } ); it("should throw error on API communication failure", async () => { userClient.client.get = jest .fn() .mockRejectedValue(new Error("Test error")); const user = userClient.getCurrent(); await expect(user).rejects.toThrowError("Test error"); }); }); describe("UserClient.create()", () => { it("should create a user", async () => { Object.defineProperty(global, "XMLHttpRequest", { value: jest.fn().mockImplementation(() => ({ response: JSON.stringify({ foo: "bar" }), open: jest.fn(), setRequestHeader: jest.fn(), getResponseHeader: jest.fn(), getAllResponseHeaders: jest.fn().mockReturnValue(""), send: jest.fn(), })), configurable: true, writable: true, }); const response = new Response(new XMLHttpRequest()); response.ok = true; response._decodedJSON = { user_id: userID, email_id: emailID, }; jest.spyOn(userClient.client, "post").mockResolvedValueOnce(response); const getInfoResponse = userClient.create(email); await expect(getInfoResponse).resolves.toBe(response._decodedJSON); expect(userClient.client.post).toHaveBeenCalledWith("/users", { email, }); }); it("should throw error when user already exists", async () => { const response = new Response(new XMLHttpRequest()); response.status = 409; jest.spyOn(userClient.client, "post").mockResolvedValue(response); const user = userClient.create(email); await expect(user).rejects.toThrow(ConflictError); }); it("should throw error when signup is disabled", async () => { const response = new Response(new XMLHttpRequest()); response.status = 403; jest.spyOn(userClient.client, "post").mockResolvedValue(response); const user = userClient.create(email); await expect(user).rejects.toThrow(ForbiddenError); }); it("should throw error if API response is not ok (no 2xx, no 4xx)", async () => { const response = new Response(new XMLHttpRequest()); jest.spyOn(userClient.client, "post").mockResolvedValue(response); const user = userClient.create(email); await expect(user).rejects.toThrow(TechnicalError); }); it("should throw error on API communication failure", async () => { userClient.client.post = jest .fn() .mockRejectedValue(new Error("Test error")); const user = userClient.create(email); await expect(user).rejects.toThrowError("Test error"); }); }); describe("UserClient.logout()", () => { it.each` status ${200} ${401} `("should return true if logout is successful", async ({ status }) => { const response = new Response(new XMLHttpRequest()); response.status = status; response.ok = status >= 200 && status <= 299; jest.spyOn(userClient.client, "post").mockResolvedValueOnce(response); await expect(userClient.logout()).resolves.not.toThrow(); expect(userClient.client.post).toHaveBeenCalledWith("/logout"); }); it.each` status | error ${400} | ${"Technical error"} ${404} | ${"Technical error"} ${500} | ${"Technical error"} `( "should throw error if API returns an error status", async ({ status, error }) => { const response = new Response(new XMLHttpRequest()); response.status = status; response.ok = status >= 200 && status <= 299; jest.spyOn(userClient.client, "post").mockResolvedValueOnce(response); await expect(userClient.logout()).rejects.toThrow(error); expect(userClient.client.post).toHaveBeenCalledWith("/logout"); } ); }); describe("UserClient.delete()", () => { it("should return true if deletion is successful", async () => { const response = new Response(new XMLHttpRequest()); response.status = 204; response.ok = true; jest.spyOn(userClient.client, "delete").mockResolvedValueOnce(response); await expect(userClient.delete()).resolves.not.toThrow(); expect(userClient.client.delete).toHaveBeenCalledWith("/user"); }); it.each` status | error ${401} | ${"Unauthorized error"} ${404} | ${"Technical error"} ${500} | ${"Technical error"} `( "should throw error if API returns an error status", async ({ status, error }) => { const response = new Response(new XMLHttpRequest()); response.status = status; response.ok = status >= 200 && status <= 299; jest.spyOn(userClient.client, "delete").mockResolvedValueOnce(response); await expect(userClient.delete()).rejects.toThrow(error); expect(userClient.client.delete).toHaveBeenCalledWith("/user"); } ); });