diff --git a/client/src/lib/classes/error-response-class.ts b/client/src/lib/classes/error-response-class.ts index fcd65fb..ee4d014 100644 --- a/client/src/lib/classes/error-response-class.ts +++ b/client/src/lib/classes/error-response-class.ts @@ -1,15 +1,18 @@ type ErrorResponseType = { status: number; message: string; + response: Response; }; export class ErrorResponse extends Error { public status: number; public message: string; + public response: Response; - constructor({ status, message }: ErrorResponseType) { + constructor({ status, message, response }: ErrorResponseType) { super(message); this.status = status; this.message = message; + this.response = response; } } diff --git a/client/src/lib/classes/generic-response-class.ts b/client/src/lib/classes/generic-response-class.ts index 5aff4a3..e5b2239 100644 --- a/client/src/lib/classes/generic-response-class.ts +++ b/client/src/lib/classes/generic-response-class.ts @@ -1,11 +1,14 @@ type GenericResponseType = { message: string; + response: Response; }; export class GenericResponse { public message: string; + public response: Response; - constructor({ message }: GenericResponseType) { + constructor({ message, response }: GenericResponseType) { this.message = message; + this.response = response; } } diff --git a/client/src/lib/classes/user-response-class.ts b/client/src/lib/classes/user-response-class.ts index b1ca91a..2252d6a 100644 --- a/client/src/lib/classes/user-response-class.ts +++ b/client/src/lib/classes/user-response-class.ts @@ -3,14 +3,17 @@ import { UserDTO } from "@/lib/DTO/user-dto"; type UserResponseType = { message: string; user: UserDTO; + response: Response; }; export class UserResponse { public message: string; public user: UserDTO; + public response: Response; - constructor({ message, user }: UserResponseType) { + constructor({ message, user, response }: UserResponseType) { this.message = message; this.user = user; + this.response = response; } } diff --git a/client/src/lib/services/auth-service.ts b/client/src/lib/services/auth-service.ts index 76c2ff0..02c490b 100644 --- a/client/src/lib/services/auth-service.ts +++ b/client/src/lib/services/auth-service.ts @@ -26,11 +26,13 @@ export const AuthService = { throw new ErrorResponse({ status: res.status, message: data.error, + response: res, }); } return new GenericResponse({ message: data.message, + response: res, }); }, signIn: async (signIn: SignInDTO) => { @@ -49,12 +51,14 @@ export const AuthService = { throw new ErrorResponse({ status: res.status, message: data.error, + response: res, }); } return new UserResponse({ message: data.message, user: data.user, + response: res, }); }, signOut: async () => { @@ -71,6 +75,7 @@ export const AuthService = { return new GenericResponse({ message: data.message, + response: res, }); }, verifyEmail: async (token: string) => { @@ -90,11 +95,13 @@ export const AuthService = { throw new ErrorResponse({ status: res.status, message: data.error, + response: res, }); } return new GenericResponse({ message: data.message, + response: res, }); }, resendVerify: async (email: string) => { @@ -110,14 +117,16 @@ export const AuthService = { const data = await res.json(); if (!res.ok) { - return new ErrorResponse({ + throw new ErrorResponse({ status: res.status, message: data.error, + response: res, }); } return new GenericResponse({ message: data.message, + response: res, }); }, verifyToken: async () => { @@ -132,12 +141,14 @@ export const AuthService = { throw new ErrorResponse({ status: res.status, message: data.error, + response: res, }); } return new UserResponse({ message: data.message, user: data.user, + response: res, }); }, forgotPassword: async (email: string) => { @@ -157,11 +168,13 @@ export const AuthService = { throw new ErrorResponse({ status: res.status, message: data.error, + response: res, }); } return new GenericResponse({ message: data.message, + response: res, }); }, resetPassword: async (reset: ResetDTO) => { @@ -185,11 +198,13 @@ export const AuthService = { throw new ErrorResponse({ status: res.status, message: data.error, + response: res, }); } return new GenericResponse({ message: data.message, + response: res, }); }, changePassword: async (change: ChangeDTO) => { @@ -207,11 +222,13 @@ export const AuthService = { throw new ErrorResponse({ status: res.status, message: data.error, + response: res, }); } return new GenericResponse({ message: data.message, + response: res, }); }, }; diff --git a/client/src/lib/services/change-password.test.ts b/client/src/lib/services/change-password.test.ts new file mode 100644 index 0000000..28b8423 --- /dev/null +++ b/client/src/lib/services/change-password.test.ts @@ -0,0 +1,90 @@ +import { describe, it, expect, vi, Mock } from "vitest"; + +import { ChangeDTO } from "@/lib/DTO/change-dto"; + +import { AuthService } from "./auth-service"; + +global.fetch = vi.fn(); + +const params: ChangeDTO = { + email: "test@example.com", + old_password: "Old_Password123", + new_password: "New_Password123", +}; + +describe("changePassword", () => { + it("should return a GenericResponse on successful change-password", async () => { + const message = "Change password success"; + const mockResponse = { message }; + + (fetch as Mock).mockResolvedValueOnce({ + ok: true, + json: vi.fn().mockResolvedValueOnce(mockResponse), + }); + + const res = await AuthService.changePassword(params); + expect(res.message).toEqual(message); + }); + + it("should throw an ErrorResponse on failed change-password", async () => { + const error = "Change password failed"; + const mockResponse = { error }; + + (fetch as Mock).mockResolvedValueOnce({ + ok: false, + status: 400, + json: vi.fn().mockResolvedValueOnce(mockResponse), + }); + + await expect(AuthService.changePassword(params)).rejects.toThrow( + expect.objectContaining({ status: 400, message: error }) + ); + }); + + it("should have content-type", async () => { + (fetch as Mock).mockResolvedValueOnce({ + ok: true, + json: vi.fn().mockResolvedValueOnce({}), + }); + + await AuthService.changePassword(params); + expect(fetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + headers: { "Content-Type": "application/json" }, + }) + ); + }); + + it("should have JSON stringified in body", async () => { + (fetch as Mock).mockResolvedValueOnce({ + ok: true, + json: vi.fn().mockResolvedValueOnce({}), + }); + + await AuthService.changePassword(params); + + expect(fetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + body: JSON.stringify(params), + }) + ); + }); + + it("should have called with POST method", async () => { + (fetch as Mock).mockResolvedValueOnce({ + ok: true, + json: vi.fn().mockResolvedValueOnce({}), + }); + + await AuthService.changePassword(params); + + expect(fetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + method: "POST", + }) + ); + }); +}); diff --git a/client/src/lib/services/forgot-password.test.ts b/client/src/lib/services/forgot-password.test.ts new file mode 100644 index 0000000..2a78a73 --- /dev/null +++ b/client/src/lib/services/forgot-password.test.ts @@ -0,0 +1,86 @@ +import { describe, it, expect, vi, Mock } from "vitest"; + +import { AuthService } from "./auth-service"; + +global.fetch = vi.fn(); + +const email = "test@example.com"; + +describe("forgotPassword", () => { + it("should return a GenericResponse on successful forgot-password", async () => { + const message = "Forgot password success"; + const mockResponse = { message }; + + (fetch as Mock).mockResolvedValueOnce({ + ok: true, + json: vi.fn().mockResolvedValueOnce(mockResponse), + }); + + const res = await AuthService.forgotPassword(email); + expect(res.message).toEqual(message); + }); + + it("should throw an ErrorResponse on failed forgot-password", async () => { + const error = "Forgot password failed"; + const mockResponse = { error }; + + (fetch as Mock).mockResolvedValueOnce({ + ok: false, + status: 400, + json: vi.fn().mockResolvedValueOnce(mockResponse), + }); + + await expect(AuthService.forgotPassword(email)).rejects.toThrow( + expect.objectContaining({ status: 400, message: error }) + ); + }); + + it("should have content-type", async () => { + (fetch as Mock).mockResolvedValueOnce({ + ok: true, + json: vi.fn().mockResolvedValueOnce({}), + }); + + await AuthService.forgotPassword(email); + expect(fetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + headers: { "Content-Type": "application/json" }, + }) + ); + }); + + it("should have JSON stringified in body", async () => { + (fetch as Mock).mockResolvedValueOnce({ + ok: true, + json: vi.fn().mockResolvedValueOnce({}), + }); + + await AuthService.forgotPassword(email); + + expect(fetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + body: JSON.stringify({ + email: email, + }), + }) + ); + }); + + it("should have called with POST method", async () => { + (fetch as Mock).mockResolvedValueOnce({ + ok: true, + json: vi.fn().mockResolvedValueOnce({}), + }); + + await AuthService.forgotPassword(email); + + expect(fetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + method: "POST", + }) + ); + }); +}); diff --git a/client/src/lib/services/resend-verify.test.ts b/client/src/lib/services/resend-verify.test.ts new file mode 100644 index 0000000..597e777 --- /dev/null +++ b/client/src/lib/services/resend-verify.test.ts @@ -0,0 +1,86 @@ +import { describe, it, expect, vi, Mock } from "vitest"; + +import { AuthService } from "./auth-service"; + +global.fetch = vi.fn(); + +const email = "test@example.com"; + +describe("resendVerify", () => { + it("should return a GenericResponse on successful resend-verify", async () => { + const message = "Resend verify success"; + const mockResponse = { message }; + + (fetch as Mock).mockResolvedValueOnce({ + ok: true, + json: vi.fn().mockResolvedValueOnce(mockResponse), + }); + + const res = await AuthService.resendVerify(email); + expect(res.message).toEqual(message); + }); + + it("should throw an ErrorResponse on failed resend-verify", async () => { + const error = "Resend verify failed"; + const mockResponse = { error }; + + (fetch as Mock).mockResolvedValueOnce({ + ok: false, + status: 400, + json: vi.fn().mockResolvedValueOnce(mockResponse), + }); + + await expect(AuthService.resendVerify(email)).rejects.toThrow( + expect.objectContaining({ status: 400, message: error }) + ); + }); + + it("should have content-type", async () => { + (fetch as Mock).mockResolvedValueOnce({ + ok: true, + json: vi.fn().mockResolvedValueOnce({}), + }); + + await AuthService.resendVerify(email); + expect(fetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + headers: { "Content-Type": "application/json" }, + }) + ); + }); + + it("should have JSON stringified in body", async () => { + (fetch as Mock).mockResolvedValueOnce({ + ok: true, + json: vi.fn().mockResolvedValueOnce({}), + }); + + await AuthService.resendVerify(email); + + expect(fetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + body: JSON.stringify({ + email: email, + }), + }) + ); + }); + + it("should have called with POST method", async () => { + (fetch as Mock).mockResolvedValueOnce({ + ok: true, + json: vi.fn().mockResolvedValueOnce({}), + }); + + await AuthService.resendVerify(email); + + expect(fetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + method: "POST", + }) + ); + }); +}); diff --git a/client/src/lib/services/reset-password.test.ts b/client/src/lib/services/reset-password.test.ts new file mode 100644 index 0000000..4cc6f45 --- /dev/null +++ b/client/src/lib/services/reset-password.test.ts @@ -0,0 +1,112 @@ +import { describe, it, expect, vi, Mock } from "vitest"; + +import { ResetDTO } from "@/lib/DTO/reset-dto"; + +import { AuthService } from "./auth-service"; +import { AppRoutes } from "@/constants/routes"; + +global.fetch = vi.fn(); + +const params: ResetDTO = { + old_password: "Old_Password123", + new_password: "New_Password123", + token: "token", +}; + +describe("resetPassword", () => { + it("should return a GenericResponse on successful reset-password", async () => { + const message = "Reset password success"; + const mockResponse = { message }; + + (fetch as Mock).mockResolvedValueOnce({ + ok: true, + json: vi.fn().mockResolvedValueOnce(mockResponse), + }); + + const res = await AuthService.resetPassword(params); + expect(res.message).toEqual(message); + }); + + it("should throw an ErrorResponse on failed reset-password", async () => { + const error = "Reset password failed"; + const mockResponse = { error }; + + (fetch as Mock).mockResolvedValueOnce({ + ok: false, + status: 400, + json: vi.fn().mockResolvedValueOnce(mockResponse), + }); + + await expect(AuthService.resetPassword(params)).rejects.toThrow( + expect.objectContaining({ status: 400, message: error }) + ); + }); + + it("should have content-type", async () => { + (fetch as Mock).mockResolvedValueOnce({ + ok: true, + json: vi.fn().mockResolvedValueOnce({}), + }); + + await AuthService.resetPassword(params); + expect(fetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + headers: { "Content-Type": "application/json" }, + }) + ); + }); + + it("should have JSON stringified in body", async () => { + (fetch as Mock).mockResolvedValueOnce({ + ok: true, + json: vi.fn().mockResolvedValueOnce({}), + }); + + await AuthService.resetPassword(params); + + expect(fetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + body: JSON.stringify({ + old_password: params.old_password, + new_password: params.new_password, + }), + }) + ); + }); + + it("should fetch with URL including the token", async () => { + const baseURL = + import.meta.env.VITE_ENV === "development" + ? `${import.meta.env.VITE_BASE_URL}/api/auth` + : "/api/auth"; + + const expectedURL = `${baseURL}${AppRoutes.ResetPassword}/${params.token}`; + + (fetch as Mock).mockResolvedValueOnce({ + ok: true, + json: vi.fn().mockResolvedValueOnce({}), + }); + + await AuthService.resetPassword(params); + + expect(fetch).toHaveBeenCalledWith(expectedURL, expect.any(Object)); + }); + + it("should have called with POST method", async () => { + (fetch as Mock).mockResolvedValueOnce({ + ok: true, + json: vi.fn().mockResolvedValueOnce({}), + }); + + await AuthService.resetPassword(params); + + expect(fetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + method: "POST", + }) + ); + }); +}); diff --git a/client/src/lib/services/sign-in.test.ts b/client/src/lib/services/sign-in.test.ts new file mode 100644 index 0000000..7308d0d --- /dev/null +++ b/client/src/lib/services/sign-in.test.ts @@ -0,0 +1,127 @@ +import { describe, it, expect, vi, Mock } from "vitest"; + +import { SignInDTO } from "@/lib/DTO/sign-in-dto"; +import { UserDTO } from "@/lib/DTO/user-dto"; + +import { AuthService } from "./auth-service"; + +global.fetch = vi.fn(); + +const params: SignInDTO = { + email: "test@example.com", + password: "Password_123", +}; + +const user: UserDTO = { + id: "abc123", + username: "Test", + email_address: "test@example.com", + last_signed_in: new Date(), + is_verified: false, + created_at: new Date(), + updated_at: new Date(), +}; + +describe("signIn", () => { + it("should return a GenericResponse on successful sign-in", async () => { + const message = "Sign in success"; + + const mockResponse = { message, user }; + (fetch as Mock).mockResolvedValueOnce({ + ok: true, + json: vi.fn().mockResolvedValueOnce(mockResponse), + }); + + const res = await AuthService.signIn(params); + expect(res.message).toEqual("Sign in success"); + expect(res.user).toEqual(user); + }); + + it("should throw an ErrorResponse on failed sign-in", async () => { + const error = "Sign in failed"; + const mockResponse = { error }; + (fetch as Mock).mockResolvedValueOnce({ + ok: false, + status: 400, + json: vi.fn().mockResolvedValueOnce(mockResponse), + }); + + await expect(AuthService.signIn(params)).rejects.toThrow( + expect.objectContaining({ status: 400, message: error }) + ); + }); + + it("should have response status 200", async () => { + (fetch as Mock).mockResolvedValueOnce({ + ok: true, + status: 200, + json: vi.fn().mockResolvedValueOnce({}), + }); + + const res = await AuthService.signIn(params); + expect(res.response.status).toEqual(200); + }); + + it("should have content-type", async () => { + (fetch as Mock).mockResolvedValueOnce({ + ok: true, + json: vi.fn().mockResolvedValueOnce({}), + }); + + await AuthService.signIn(params); + expect(fetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + headers: { "Content-Type": "application/json" }, + }) + ); + }); + + it("should have JSON stringified in body", async () => { + (fetch as Mock).mockResolvedValueOnce({ + ok: true, + json: vi.fn().mockResolvedValueOnce({}), + }); + + await AuthService.signIn(params); + + expect(fetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + body: JSON.stringify(params), + }) + ); + }); + + it("should have called with POST method", async () => { + (fetch as Mock).mockResolvedValueOnce({ + ok: true, + json: vi.fn().mockResolvedValueOnce({}), + }); + + await AuthService.signIn(params); + + expect(fetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + method: "POST", + }) + ); + }); + + it("should include credentials", async () => { + (fetch as Mock).mockResolvedValueOnce({ + ok: true, + json: vi.fn().mockResolvedValueOnce({}), + }); + + await AuthService.signIn(params); + + expect(fetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + credentials: "include", + }) + ); + }); +}); diff --git a/client/src/lib/services/sign-out.test.ts b/client/src/lib/services/sign-out.test.ts new file mode 100644 index 0000000..30791f3 --- /dev/null +++ b/client/src/lib/services/sign-out.test.ts @@ -0,0 +1,71 @@ +import { describe, it, expect, vi, Mock } from "vitest"; + +import { AuthService } from "./auth-service"; + +global.fetch = vi.fn(); + +describe("signOut", () => { + it("should return a GenericResponse on successful sign-out", async () => { + const message = "Sign out success"; + const mockResponse = { message }; + (fetch as Mock).mockResolvedValueOnce({ + ok: true, + json: vi.fn().mockResolvedValueOnce(mockResponse), + }); + + const res = await AuthService.signOut(); + expect(res.message).toEqual(message); + }); + + it("should throw an ErrorResponse on failed sign-out", async () => { + (fetch as Mock).mockResolvedValueOnce({ + ok: false, + json: vi.fn().mockResolvedValueOnce({}), + }); + + await expect(AuthService.signOut()).rejects.toThrow("Unable to sign out"); + }); + + it("should have response status 200", async () => { + (fetch as Mock).mockResolvedValueOnce({ + ok: true, + status: 200, + json: vi.fn().mockResolvedValueOnce({}), + }); + + const res = await AuthService.signOut(); + expect(res.response.status).toEqual(200); + }); + + it("should have called with POST method", async () => { + (fetch as Mock).mockResolvedValueOnce({ + ok: true, + json: vi.fn().mockResolvedValueOnce({}), + }); + + await AuthService.signOut(); + + expect(fetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + method: "POST", + }) + ); + }); + + it("should include credentials", async () => { + (fetch as Mock).mockResolvedValueOnce({ + ok: true, + json: vi.fn().mockResolvedValueOnce({}), + }); + + await AuthService.signOut(); + + expect(fetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + credentials: "include", + }) + ); + }); +}); diff --git a/client/src/lib/services/sign-up.test.ts b/client/src/lib/services/sign-up.test.ts new file mode 100644 index 0000000..3ab0741 --- /dev/null +++ b/client/src/lib/services/sign-up.test.ts @@ -0,0 +1,103 @@ +import { describe, it, expect, vi, Mock } from "vitest"; + +import { SignUpDTO } from "@/lib/DTO/sign-up-dto"; + +import { AuthService } from "./auth-service"; + +global.fetch = vi.fn(); + +const params: SignUpDTO = { + username: "Test", + email: "test@example.com", + password: "Password_123", +}; + +describe("signUp", () => { + it("should return a GenericResponse on successful sign-up", async () => { + const message = "Sign up success"; + const mockResponse = { message }; + + (fetch as Mock).mockResolvedValueOnce({ + ok: true, + json: vi.fn().mockResolvedValueOnce(mockResponse), + }); + + const res = await AuthService.signUp(params); + expect(res.message).toEqual(message); + }); + + it("should throw an ErrorResponse on failed sign-up", async () => { + const error = "Sign up failed"; + const mockResponse = { error }; + + (fetch as Mock).mockResolvedValueOnce({ + ok: false, + status: 400, + json: vi.fn().mockResolvedValueOnce(mockResponse), + }); + + await expect(AuthService.signUp(params)).rejects.toThrow( + expect.objectContaining({ status: 400, message: error }) + ); + }); + + it("should have response status 200", async () => { + (fetch as Mock).mockResolvedValueOnce({ + ok: true, + status: 200, + json: vi.fn().mockResolvedValueOnce({}), + }); + + const res = await AuthService.signUp(params); + expect(res.response.status).toEqual(200); + }); + + it("should have content-type", async () => { + (fetch as Mock).mockResolvedValueOnce({ + ok: true, + json: vi.fn().mockResolvedValueOnce({}), + }); + + await AuthService.signUp(params); + expect(fetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + headers: { "Content-Type": "application/json" }, + }) + ); + }); + + it("should have JSON stringified in body", async () => { + (fetch as Mock).mockResolvedValueOnce({ + ok: true, + status: 200, + json: vi.fn().mockResolvedValueOnce({}), + }); + + await AuthService.signUp(params); + + expect(fetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + body: JSON.stringify(params), + }) + ); + }); + + it("should have called with POST method", async () => { + (fetch as Mock).mockResolvedValueOnce({ + ok: true, + status: 200, + json: vi.fn().mockResolvedValueOnce({}), + }); + + await AuthService.signUp(params); + + expect(fetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + method: "POST", + }) + ); + }); +}); diff --git a/client/src/lib/services/verify-email.test.ts b/client/src/lib/services/verify-email.test.ts new file mode 100644 index 0000000..54fc4cc --- /dev/null +++ b/client/src/lib/services/verify-email.test.ts @@ -0,0 +1,97 @@ +import { describe, it, expect, vi, Mock } from "vitest"; + +import { AuthService } from "./auth-service"; + +global.fetch = vi.fn(); + +const token = "token"; + +describe("verifyEmail", () => { + it("should return a GenericResponse on successful verify-email", async () => { + const message = "Verify email success"; + const mockResponse = { message }; + + (fetch as Mock).mockResolvedValueOnce({ + ok: true, + json: vi.fn().mockResolvedValueOnce(mockResponse), + }); + + const res = await AuthService.verifyEmail(token); + expect(res.message).toEqual(message); + }); + + it("should throw an ErrorResponse on failed sign-up", async () => { + const error = "Verify email failed"; + const mockResponse = { error }; + + (fetch as Mock).mockResolvedValueOnce({ + ok: false, + status: 400, + json: vi.fn().mockResolvedValueOnce(mockResponse), + }); + + await expect(AuthService.verifyEmail(token)).rejects.toThrow( + expect.objectContaining({ status: 400, message: error }) + ); + }); + + it("should have response status 200", async () => { + (fetch as Mock).mockResolvedValueOnce({ + ok: true, + status: 200, + json: vi.fn().mockResolvedValueOnce({}), + }); + + const res = await AuthService.verifyEmail(token); + expect(res.response.status).toEqual(200); + }); + + it("should have content-type", async () => { + (fetch as Mock).mockResolvedValueOnce({ + ok: true, + json: vi.fn().mockResolvedValueOnce({}), + }); + + await AuthService.verifyEmail(token); + expect(fetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + headers: { "Content-Type": "application/json" }, + }) + ); + }); + + it("should have JSON stringified in body", async () => { + (fetch as Mock).mockResolvedValueOnce({ + ok: true, + json: vi.fn().mockResolvedValueOnce({}), + }); + + await AuthService.verifyEmail(token); + + expect(fetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + body: JSON.stringify({ + token: token, + }), + }) + ); + }); + + it("should have called with POST method", async () => { + (fetch as Mock).mockResolvedValueOnce({ + ok: true, + json: vi.fn().mockResolvedValueOnce({}), + }); + + await AuthService.verifyEmail(token); + + expect(fetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + method: "POST", + }) + ); + }); +}); diff --git a/client/src/lib/services/verify-token.test.ts b/client/src/lib/services/verify-token.test.ts new file mode 100644 index 0000000..489d1d8 --- /dev/null +++ b/client/src/lib/services/verify-token.test.ts @@ -0,0 +1,67 @@ +import { describe, it, expect, vi, Mock } from "vitest"; + +import { AuthService } from "./auth-service"; + +global.fetch = vi.fn(); + +describe("verifyToken", () => { + it("should return a GenericResponse on successful verify-token", async () => { + const message = "Verify token success"; + const mockResponse = { message }; + + (fetch as Mock).mockResolvedValueOnce({ + ok: true, + json: vi.fn().mockResolvedValueOnce(mockResponse), + }); + + const res = await AuthService.verifyToken(); + expect(res.message).toEqual(message); + }); + + it("should throw an ErrorResponse on failed verify-token", async () => { + const error = "Verify token failed"; + const mockResponse = { error }; + + (fetch as Mock).mockResolvedValueOnce({ + ok: false, + status: 400, + json: vi.fn().mockResolvedValueOnce(mockResponse), + }); + + await expect(AuthService.verifyToken()).rejects.toThrow( + expect.objectContaining({ status: 400, message: error }) + ); + }); + + it("should have called with GET method", async () => { + (fetch as Mock).mockResolvedValueOnce({ + ok: true, + json: vi.fn().mockResolvedValueOnce({}), + }); + + await AuthService.verifyToken(); + + expect(fetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + method: "GET", + }) + ); + }); + + it("should include credentials", async () => { + (fetch as Mock).mockResolvedValueOnce({ + ok: true, + json: vi.fn().mockResolvedValueOnce({}), + }); + + await AuthService.verifyToken(); + + expect(fetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + credentials: "include", + }) + ); + }); +});