From 9302013632c673f022613bb70d11b220ba297c1c Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Tue, 17 Mar 2026 10:00:51 +0300 Subject: [PATCH] feat(auth): create login api call with basic test coverage and msw mock --- src/features/auth/api/calls/index.ts | 1 + .../auth/api/calls/login/constants.ts | 6 ++++ src/features/auth/api/calls/login/index.ts | 2 ++ .../auth/api/calls/login/login.mock.ts | 27 +++++++++++++++++ .../auth/api/calls/login/login.spec.ts | 29 +++++++++++++++++++ src/features/auth/api/calls/login/login.ts | 13 +++++++++ src/features/auth/model/types/service.ts | 11 +++++++ 7 files changed, 89 insertions(+) create mode 100644 src/features/auth/api/calls/index.ts create mode 100644 src/features/auth/api/calls/login/constants.ts create mode 100644 src/features/auth/api/calls/login/index.ts create mode 100644 src/features/auth/api/calls/login/login.mock.ts create mode 100644 src/features/auth/api/calls/login/login.spec.ts create mode 100644 src/features/auth/api/calls/login/login.ts diff --git a/src/features/auth/api/calls/index.ts b/src/features/auth/api/calls/index.ts new file mode 100644 index 0000000..f54d11b --- /dev/null +++ b/src/features/auth/api/calls/index.ts @@ -0,0 +1 @@ +export * from "./login"; diff --git a/src/features/auth/api/calls/login/constants.ts b/src/features/auth/api/calls/login/constants.ts new file mode 100644 index 0000000..fe1c15c --- /dev/null +++ b/src/features/auth/api/calls/login/constants.ts @@ -0,0 +1,6 @@ +export const LOGIN_API_ROUTE = "auth/login"; + +// MOCKS +export const MOCK_EMAIL = "test@test.com"; +export const MOCK_PASSWORD = "password"; +export const MOCK_TOKEN = "mock.access.token"; diff --git a/src/features/auth/api/calls/login/index.ts b/src/features/auth/api/calls/login/index.ts new file mode 100644 index 0000000..31b1f9b --- /dev/null +++ b/src/features/auth/api/calls/login/index.ts @@ -0,0 +1,2 @@ +export { login } from './login'; +export { LOGIN_API_ROUTE } from './constants'; \ No newline at end of file diff --git a/src/features/auth/api/calls/login/login.mock.ts b/src/features/auth/api/calls/login/login.mock.ts new file mode 100644 index 0000000..4d3b5c1 --- /dev/null +++ b/src/features/auth/api/calls/login/login.mock.ts @@ -0,0 +1,27 @@ +import { http, HttpResponse } from "msw"; +import type { AuthData } from "../../../model/types/service"; +import { BASE_URL } from "shared/config"; +import { + LOGIN_API_ROUTE, + MOCK_EMAIL, + MOCK_PASSWORD, + MOCK_TOKEN, +} from "./constants"; + +const LOGIN_URL = `${BASE_URL}/${LOGIN_API_ROUTE}`; + +/** + * Msw interceptor. Mocks the login endpoint response. + */ +export const loginMock = http.post(LOGIN_URL, async ({ request }) => { + const { email, password } = (await request.json()) as AuthData; + + if (email === MOCK_EMAIL && password === MOCK_PASSWORD) { + return HttpResponse.json({ + accessToken: MOCK_TOKEN, + user: { id: "1", email }, + }); + } + + return HttpResponse.json({ message: "Invalid credentials" }, { status: 401 }); +}); diff --git a/src/features/auth/api/calls/login/login.spec.ts b/src/features/auth/api/calls/login/login.spec.ts new file mode 100644 index 0000000..851e0ea --- /dev/null +++ b/src/features/auth/api/calls/login/login.spec.ts @@ -0,0 +1,29 @@ +import { setupServer } from "msw/node"; +import { login } from "./login"; +import { loginMock } from "./login.mock"; +import { MOCK_EMAIL, MOCK_PASSWORD } from "./constants"; + +const server = setupServer(loginMock); + +describe("login", () => { + beforeAll(() => server.listen({ onUnhandledRequest: "error" })); + afterEach(() => server.resetHandlers()); + afterAll(() => server.close()); + + describe("happy path", () => { + it("returns access token and user on valid credentials", async () => { + const result = await login({ email: MOCK_EMAIL, password: MOCK_PASSWORD }); + + expect(result.accessToken).toBe("mock.access.token"); + expect(result.user).toEqual({ id: "1", email: MOCK_EMAIL }); + }); + }); + + describe("error cases", () => { + it("throws on invalid credentials", async () => { + await expect( + login({ email: "wrong@test.com", password: "wrong" }), + ).rejects.toThrow(); + }); + }); +}); diff --git a/src/features/auth/api/calls/login/login.ts b/src/features/auth/api/calls/login/login.ts new file mode 100644 index 0000000..42b8fab --- /dev/null +++ b/src/features/auth/api/calls/login/login.ts @@ -0,0 +1,13 @@ +import { api } from "../../../api"; +import type { AuthData, AuthResponse } from "../../../model/types/service"; +import { LOGIN_API_ROUTE } from "./constants"; + +/** + * Logs in a user with the given email and password. + * + * @param loginData - The user's login data (email and password). + * @returns A promise that resolves to the authentication response. + */ +export function login(loginData: AuthData) { + return api.post(LOGIN_API_ROUTE, { json: loginData }).json(); +} diff --git a/src/features/auth/model/types/service.ts b/src/features/auth/model/types/service.ts index 2c735cb..1cc1346 100644 --- a/src/features/auth/model/types/service.ts +++ b/src/features/auth/model/types/service.ts @@ -1,5 +1,16 @@ import type { User } from "entities/User"; +export interface AuthData { + /** + * User's email address. + */ + email: string; + /** + * User's password. + */ + password: string; +} + export interface AuthResponse { /** * Access token for the authenticated user.