diff --git a/src/features/auth/api/calls/index.ts b/src/features/auth/api/calls/index.ts index ce51756..d030e96 100644 --- a/src/features/auth/api/calls/index.ts +++ b/src/features/auth/api/calls/index.ts @@ -1,3 +1,4 @@ export * from "./login"; export * from "./register"; export * from "./logout"; +export * from "./refresh"; diff --git a/src/features/auth/api/calls/refresh/constants.ts b/src/features/auth/api/calls/refresh/constants.ts new file mode 100644 index 0000000..99cadcb --- /dev/null +++ b/src/features/auth/api/calls/refresh/constants.ts @@ -0,0 +1,3 @@ +export const REFRESH_API_ROUTE = "auth/refresh"; +export const MOCK_TOKEN = "mock.access.token"; +export const MOCK_FRESH_TOKEN = "mock.fresh.access.token"; diff --git a/src/features/auth/api/calls/refresh/index.ts b/src/features/auth/api/calls/refresh/index.ts new file mode 100644 index 0000000..1f69e73 --- /dev/null +++ b/src/features/auth/api/calls/refresh/index.ts @@ -0,0 +1,2 @@ +export { refresh } from "./refresh"; +export { REFRESH_API_ROUTE } from "./constants"; diff --git a/src/features/auth/api/calls/refresh/refresh.mock.ts b/src/features/auth/api/calls/refresh/refresh.mock.ts new file mode 100644 index 0000000..ca4cc7a --- /dev/null +++ b/src/features/auth/api/calls/refresh/refresh.mock.ts @@ -0,0 +1,22 @@ +import { http, HttpResponse } from "msw"; +import { BASE_URL } from "shared/config"; +import { MOCK_FRESH_TOKEN, MOCK_TOKEN, REFRESH_API_ROUTE } from "./constants"; + +const REFRESH_URL = `${BASE_URL}/${REFRESH_API_ROUTE}`; + +/** + * Msw interceptor. Mocks the refresh endpoint response. + * Validates the Authorization header — returns a fresh token on success, 401 on expired/missing session. + */ +export const refreshMock = http.post(REFRESH_URL, ({ request }) => { + const authHeader = request.headers.get("Authorization"); + + if (authHeader === `Bearer ${MOCK_TOKEN}`) { + return HttpResponse.json({ + accessToken: MOCK_FRESH_TOKEN, + user: { id: "1", email: "test@test.com" }, + }); + } + + return HttpResponse.json({ message: "Session expired" }, { status: 401 }); +}); diff --git a/src/features/auth/api/calls/refresh/refresh.spec.ts b/src/features/auth/api/calls/refresh/refresh.spec.ts new file mode 100644 index 0000000..f8e6ea7 --- /dev/null +++ b/src/features/auth/api/calls/refresh/refresh.spec.ts @@ -0,0 +1,33 @@ +import { setupServer } from "msw/node"; +import { useAuthStore } from "../../../model"; +import { refresh } from "./refresh"; +import { refreshMock } from "./refresh.mock"; +import { MOCK_FRESH_TOKEN, MOCK_TOKEN } from "./constants"; + +const server = setupServer(refreshMock); + +describe("refresh", () => { + beforeAll(() => server.listen({ onUnhandledRequest: "error" })); + afterEach(() => { + server.resetHandlers(); + useAuthStore.setState({ accessToken: undefined }); + }); + afterAll(() => server.close()); + + describe("happy path", () => { + it("returns a fresh access token and user when session is valid", async () => { + useAuthStore.setState({ accessToken: MOCK_TOKEN }); + + const result = await refresh(); + + expect(result.accessToken).toBe(MOCK_FRESH_TOKEN); + expect(result.user).toEqual({ id: "1", email: "test@test.com" }); + }); + }); + + describe("error cases", () => { + it("throws when session is expired or missing", async () => { + await expect(refresh()).rejects.toThrow(); + }); + }); +}); diff --git a/src/features/auth/api/calls/refresh/refresh.ts b/src/features/auth/api/calls/refresh/refresh.ts new file mode 100644 index 0000000..d69d7d6 --- /dev/null +++ b/src/features/auth/api/calls/refresh/refresh.ts @@ -0,0 +1,7 @@ +import { api } from "../../../api"; +import type { AuthResponse } from "../../../model/types/service"; +import { REFRESH_API_ROUTE } from "./constants"; + +export function refresh() { + return api.post(REFRESH_API_ROUTE).json(); +}