diff --git a/src/frontend/apps/desk/.env.development b/src/frontend/apps/desk/.env.development index 96383c2..45ee232 100644 --- a/src/frontend/apps/desk/.env.development +++ b/src/frontend/apps/desk/.env.development @@ -1 +1 @@ -NEXT_PUBLIC_API_URL=http://localhost:8071/api/v1.0/ +NEXT_PUBLIC_API_ORIGIN=http://localhost:8071 diff --git a/src/frontend/apps/desk/.env.production b/src/frontend/apps/desk/.env.production deleted file mode 100644 index ba16e0c..0000000 --- a/src/frontend/apps/desk/.env.production +++ /dev/null @@ -1 +0,0 @@ -NEXT_PUBLIC_API_URL=https://desk-staging.beta.numerique.gouv.fr/api/v1.0/ diff --git a/src/frontend/apps/desk/.env.test b/src/frontend/apps/desk/.env.test index eda49a1..9a4d514 100644 --- a/src/frontend/apps/desk/.env.test +++ b/src/frontend/apps/desk/.env.test @@ -1 +1 @@ -NEXT_PUBLIC_API_URL=/api/ +NEXT_PUBLIC_API_ORIGIN=http://test.jest diff --git a/src/frontend/apps/desk/src/api/__tests__/fetchApi.test.tsx b/src/frontend/apps/desk/src/api/__tests__/fetchApi.test.tsx index 1be911b..6626aa8 100644 --- a/src/frontend/apps/desk/src/api/__tests__/fetchApi.test.tsx +++ b/src/frontend/apps/desk/src/api/__tests__/fetchApi.test.tsx @@ -5,22 +5,19 @@ import { useAuthStore } from '@/core/auth'; describe('fetchAPI', () => { beforeEach(() => { - process.env.NEXT_PUBLIC_API_URL = 'http://some.api.url/api/v1.0/'; fetchMock.restore(); }); it('adds correctly the basename', () => { - fetchMock.mock('http://some.api.url/api/v1.0/some/url', 200); + fetchMock.mock('http://test.jest/api/v1.0/some/url', 200); void fetchAPI('some/url'); - expect(fetchMock.lastUrl()).toEqual( - 'http://some.api.url/api/v1.0/some/url', - ); + expect(fetchMock.lastUrl()).toEqual('http://test.jest/api/v1.0/some/url'); }); it('adds the credentials automatically', () => { - fetchMock.mock('http://some.api.url/api/v1.0/some/url', 200); + fetchMock.mock('http://test.jest/api/v1.0/some/url', 200); void fetchAPI('some/url', { body: 'some body' }); @@ -39,7 +36,7 @@ describe('fetchAPI', () => { .spyOn(useAuthStore.getState(), 'logout') .mockImplementation(logoutMock); - fetchMock.mock('http://some.api.url/api/v1.0/some/url', 401); + fetchMock.mock('http://test.jest/api/v1.0/some/url', 401); await fetchAPI('some/url'); expect(logoutMock).toHaveBeenCalled(); diff --git a/src/frontend/apps/desk/src/api/conf.ts b/src/frontend/apps/desk/src/api/conf.ts new file mode 100644 index 0000000..d4c1838 --- /dev/null +++ b/src/frontend/apps/desk/src/api/conf.ts @@ -0,0 +1,7 @@ +export const baseApiUrl = (apiVersion: string = '1.0') => { + const origin = + process.env.NEXT_PUBLIC_API_ORIGIN || + (typeof window !== 'undefined' ? window.location.origin : ''); + + return `${origin}/api/v${apiVersion}/`; +}; diff --git a/src/frontend/apps/desk/src/api/fetchApi.ts b/src/frontend/apps/desk/src/api/fetchApi.ts index 48b381b..1b84bbe 100644 --- a/src/frontend/apps/desk/src/api/fetchApi.ts +++ b/src/frontend/apps/desk/src/api/fetchApi.ts @@ -1,5 +1,7 @@ import { useAuthStore } from '@/core/auth'; +import { baseApiUrl } from './conf'; + /** * Retrieves the CSRF token from the document's cookies. * @@ -13,9 +15,12 @@ function getCSRFToken() { .pop(); } -export const fetchAPI = async (input: string, init?: RequestInit) => { - const apiUrl = `${process.env.NEXT_PUBLIC_API_URL}${input}`; - const { logout } = useAuthStore.getState(); +export const fetchAPI = async ( + input: string, + init?: RequestInit, + apiVersion = '1.0', +) => { + const apiUrl = `${baseApiUrl(apiVersion)}${input}`; const csrfToken = getCSRFToken(); @@ -30,6 +35,7 @@ export const fetchAPI = async (input: string, init?: RequestInit) => { }); if (response.status === 401) { + const { logout } = useAuthStore.getState(); logout(); } diff --git a/src/frontend/apps/desk/src/api/index.ts b/src/frontend/apps/desk/src/api/index.ts index c8c14e4..c79db68 100644 --- a/src/frontend/apps/desk/src/api/index.ts +++ b/src/frontend/apps/desk/src/api/index.ts @@ -1,4 +1,5 @@ export * from './APIError'; +export * from './conf'; export * from './fetchApi'; export * from './types'; export * from './utils'; diff --git a/src/frontend/apps/desk/src/core/auth/useAuthStore.tsx b/src/frontend/apps/desk/src/core/auth/useAuthStore.tsx index 27174db..3de45ea 100644 --- a/src/frontend/apps/desk/src/core/auth/useAuthStore.tsx +++ b/src/frontend/apps/desk/src/core/auth/useAuthStore.tsx @@ -1,12 +1,8 @@ import { create } from 'zustand'; -import { User, getMe } from './api'; +import { baseApiUrl } from '@/api'; -export const login = () => { - window.location.replace( - new URL('authenticate/', process.env.NEXT_PUBLIC_API_URL).href, - ); -}; +import { User, getMe } from './api'; interface AuthStore { authenticated: boolean; @@ -30,12 +26,10 @@ export const useAuthStore = create((set) => ({ set({ authenticated: true, userData: data }); }) .catch(() => { - login(); + window.location.replace(new URL('authenticate/', baseApiUrl()).href); }); }, logout: () => { - window.location.replace( - new URL('logout/', process.env.NEXT_PUBLIC_API_URL).href, - ); + window.location.replace(new URL('logout/', baseApiUrl()).href); }, })); diff --git a/src/frontend/apps/desk/src/custom-next.d.ts b/src/frontend/apps/desk/src/custom-next.d.ts index 5919942..a54bc2a 100644 --- a/src/frontend/apps/desk/src/custom-next.d.ts +++ b/src/frontend/apps/desk/src/custom-next.d.ts @@ -19,6 +19,6 @@ declare module '*.svg?url' { namespace NodeJS { interface ProcessEnv { - NEXT_PUBLIC_API_URL?: string; + NEXT_PUBLIC_API_ORIGIN?: string; } } diff --git a/src/frontend/apps/desk/src/features/members/__tests__/MemberGrid.test.tsx b/src/frontend/apps/desk/src/features/members/__tests__/MemberGrid.test.tsx index 04e70d3..8c001a3 100644 --- a/src/frontend/apps/desk/src/features/members/__tests__/MemberGrid.test.tsx +++ b/src/frontend/apps/desk/src/features/members/__tests__/MemberGrid.test.tsx @@ -20,7 +20,7 @@ describe('MemberGrid', () => { }); it('renders with no member to display', async () => { - fetchMock.mock(`/api/teams/123456/accesses/?page=1`, { + fetchMock.mock(`end:/teams/123456/accesses/?page=1`, { count: 0, results: [], }); @@ -76,7 +76,7 @@ describe('MemberGrid', () => { }, ]; - fetchMock.mock(`/api/teams/123456/accesses/?page=1`, { + fetchMock.mock(`end:/teams/123456/accesses/?page=1`, { count: 3, results: accesses, }); @@ -99,7 +99,8 @@ describe('MemberGrid', () => { }); it('checks the pagination', async () => { - fetchMock.get(`begin:/api/teams/123456/accesses/?page=`, { + const regexp = new RegExp(/.*\/teams\/123456\/accesses\/\?page=.*/); + fetchMock.get(regexp, { count: 40, results: Array.from({ length: 20 }, (_, i) => ({ id: i, @@ -119,7 +120,7 @@ describe('MemberGrid', () => { expect(screen.getByRole('status')).toBeInTheDocument(); - expect(fetchMock.lastUrl()).toBe('/api/teams/123456/accesses/?page=1'); + expect(fetchMock.lastUrl()).toContain('/teams/123456/accesses/?page=1'); expect( await screen.findByLabelText('You are currently on page 1'), @@ -131,7 +132,7 @@ describe('MemberGrid', () => { await screen.findByLabelText('You are currently on page 2'), ).toBeInTheDocument(); - expect(fetchMock.lastUrl()).toBe('/api/teams/123456/accesses/?page=2'); + expect(fetchMock.lastUrl()).toContain('/teams/123456/accesses/?page=2'); }); [ @@ -149,7 +150,8 @@ describe('MemberGrid', () => { }, ].forEach(({ role, expected }) => { it(`checks action button when ${role}`, async () => { - fetchMock.get(`begin:/api/teams/123456/accesses/?page=`, { + const regexp = new RegExp(/.*\/teams\/123456\/accesses\/\?page=.*/); + fetchMock.get(regexp, { count: 1, results: [ { @@ -190,7 +192,7 @@ describe('MemberGrid', () => { }); it('controls the render when api error', async () => { - fetchMock.mock(`/api/teams/123456/accesses/?page=1`, { + fetchMock.mock(`end:/teams/123456/accesses/?page=1`, { status: 500, body: { cause: 'All broken :(', @@ -207,7 +209,7 @@ describe('MemberGrid', () => { }); it('cannot add members when current role is member', () => { - fetchMock.get(`/api/teams/123456/accesses/?page=1`, 200); + fetchMock.get(`end:/teams/123456/accesses/?page=1`, 200); render(, { wrapper: AppWrapper, @@ -261,17 +263,17 @@ describe('MemberGrid', () => { ); const reversedMockedData = [...sortedMockedData].reverse(); - fetchMock.get(`/api/teams/123456/accesses/?page=1`, { + fetchMock.get(`end:/teams/123456/accesses/?page=1`, { count: 3, results: mockedData, }); - fetchMock.get(`/api/teams/123456/accesses/?page=1&ordering=${ordering}`, { + fetchMock.get(`end:/teams/123456/accesses/?page=1&ordering=${ordering}`, { count: 3, results: sortedMockedData, }); - fetchMock.get(`/api/teams/123456/accesses/?page=1&ordering=-${ordering}`, { + fetchMock.get(`end:/teams/123456/accesses/?page=1&ordering=-${ordering}`, { count: 3, results: reversedMockedData, }); @@ -282,7 +284,7 @@ describe('MemberGrid', () => { expect(screen.getByRole('status')).toBeInTheDocument(); - expect(fetchMock.lastUrl()).toBe(`/api/teams/123456/accesses/?page=1`); + expect(fetchMock.lastUrl()).toContain(`/teams/123456/accesses/?page=1`); await waitFor(() => { expect(screen.queryByRole('status')).not.toBeInTheDocument(); @@ -298,8 +300,8 @@ describe('MemberGrid', () => { await userEvent.click(screen.getByText(header_name)); - expect(fetchMock.lastUrl()).toBe( - `/api/teams/123456/accesses/?page=1&ordering=${ordering}`, + expect(fetchMock.lastUrl()).toContain( + `/teams/123456/accesses/?page=1&ordering=${ordering}`, ); await waitFor(() => { @@ -315,8 +317,8 @@ describe('MemberGrid', () => { await userEvent.click(screen.getByText(header_name)); - expect(fetchMock.lastUrl()).toBe( - `/api/teams/123456/accesses/?page=1&ordering=-${ordering}`, + expect(fetchMock.lastUrl()).toContain( + `/teams/123456/accesses/?page=1&ordering=-${ordering}`, ); await waitFor(() => { expect(screen.queryByRole('status')).not.toBeInTheDocument(); @@ -331,7 +333,7 @@ describe('MemberGrid', () => { await userEvent.click(screen.getByText(header_name)); - expect(fetchMock.lastUrl()).toBe('/api/teams/123456/accesses/?page=1'); + expect(fetchMock.lastUrl()).toContain('/teams/123456/accesses/?page=1'); await waitFor(() => { expect(screen.queryByRole('status')).not.toBeInTheDocument(); diff --git a/src/frontend/apps/desk/src/features/members/__tests__/ModalRole.test.tsx b/src/frontend/apps/desk/src/features/members/__tests__/ModalRole.test.tsx index c1d52ab..24316a0 100644 --- a/src/frontend/apps/desk/src/features/members/__tests__/ModalRole.test.tsx +++ b/src/frontend/apps/desk/src/features/members/__tests__/ModalRole.test.tsx @@ -66,7 +66,7 @@ describe('ModalRole', () => { }); it('updates the role successfully', async () => { - fetchMock.patchOnce(`/api/teams/123/accesses/789/`, { + fetchMock.mock(`end:/teams/123/accesses/789/`, { status: 200, ok: true, }); @@ -110,13 +110,13 @@ describe('ModalRole', () => { ); }); - expect(fetchMock.lastUrl()).toBe(`/api/teams/123/accesses/789/`); + expect(fetchMock.lastUrl()).toContain(`/teams/123/accesses/789/`); expect(onClose).toHaveBeenCalled(); }); it('fails to update the role', async () => { - fetchMock.patchOnce(`/api/teams/123/accesses/789/`, { + fetchMock.patchOnce(`end:/teams/123/accesses/789/`, { status: 500, body: { detail: 'The server is totally broken', diff --git a/src/frontend/apps/desk/src/features/teams/__tests__/PanelTeams.test.tsx b/src/frontend/apps/desk/src/features/teams/__tests__/PanelTeams.test.tsx index 585ac30..4614df5 100644 --- a/src/frontend/apps/desk/src/features/teams/__tests__/PanelTeams.test.tsx +++ b/src/frontend/apps/desk/src/features/teams/__tests__/PanelTeams.test.tsx @@ -23,7 +23,7 @@ describe('PanelTeams', () => { }); it('renders with no team to display', async () => { - fetchMock.mock(`/api/teams/?page=1&ordering=-created_at`, { + fetchMock.mock(`end:/teams/?page=1&ordering=-created_at`, { count: 0, results: [], }); @@ -40,7 +40,7 @@ describe('PanelTeams', () => { }); it('renders an empty team', async () => { - fetchMock.mock(`/api/teams/?page=1&ordering=-created_at`, { + fetchMock.mock(`end:/teams/?page=1&ordering=-created_at`, { count: 1, results: [ { @@ -61,7 +61,7 @@ describe('PanelTeams', () => { }); it('renders a team with only 1 member', async () => { - fetchMock.mock(`/api/teams/?page=1&ordering=-created_at`, { + fetchMock.mock(`end:/teams/?page=1&ordering=-created_at`, { count: 1, results: [ { @@ -87,7 +87,7 @@ describe('PanelTeams', () => { }); it('renders a non-empty team', async () => { - fetchMock.mock(`/api/teams/?page=1&ordering=-created_at`, { + fetchMock.mock(`end:/teams/?page=1&ordering=-created_at`, { count: 1, results: [ { @@ -115,7 +115,7 @@ describe('PanelTeams', () => { }); it('renders the error', async () => { - fetchMock.mock(`/api/teams/?page=1&ordering=-created_at`, { + fetchMock.mock(`end:/teams/?page=1&ordering=-created_at`, { status: 500, }); @@ -131,7 +131,7 @@ describe('PanelTeams', () => { }); it('renders with team panel open', async () => { - fetchMock.mock(`/api/teams/?page=1&ordering=-created_at`, { + fetchMock.mock(`end:/teams/?page=1&ordering=-created_at`, { count: 1, results: [], }); @@ -146,7 +146,7 @@ describe('PanelTeams', () => { }); it('closes and opens the team panel', async () => { - fetchMock.mock(`/api/teams/?page=1&ordering=-created_at`, { + fetchMock.get(`end:/teams/?page=1&ordering=-created_at`, { count: 1, results: [], }); diff --git a/src/helm/env.d/dev/values.desk.yaml.gotmpl b/src/helm/env.d/dev/values.desk.yaml.gotmpl index bad0020..6977b4b 100644 --- a/src/helm/env.d/dev/values.desk.yaml.gotmpl +++ b/src/helm/env.d/dev/values.desk.yaml.gotmpl @@ -60,7 +60,7 @@ backend: frontend: envVars: PORT: 8080 - NEXT_PUBLIC_API_URL: https://desk.127.0.0.1.nip.io/api/v1.0/ + NEXT_PUBLIC_API_ORIGIN: https://desk.127.0.0.1.nip.io replicas: 1 command: