fix(unit-2-auth): Fix API endpoint paths and JSON casing for real backend

- Add /api/v1/ prefix to all Unit 2 API calls (Setup/status, Setup/owner,
  Invitation/validate, Invitation/complete) to match backend ApiPrefixConvention
- Fix setup status endpoint to POST /api/v1/Setup/owner (not /Setup)
- Handle PascalCase 'Initialized' response from .NET backend without camelCase policy
- Update all MSW mock handlers to match corrected /api/v1/ URL patterns
- Remove unused imports from RouteGuard.test.tsx

Root cause: backend uses ApiPrefixConvention('api/v1') but frontend calls
were missing the prefix, and .NET defaults to PascalCase JSON serialization.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-06-22 10:55:34 +02:00
co-authored by Claude Haiku 4.5
parent c9cd0ff457
commit 8d95754ece
6 changed files with 15 additions and 14 deletions
+2 -2
View File
@@ -35,7 +35,7 @@ export function useValidateInvitation(token: string | null) {
setIsPending(true);
setError(null);
try {
const url = `/Invitation/validate?token=${encodeURIComponent(token)}`;
const url = `/api/v1/Invitation/validate?token=${encodeURIComponent(token)}`;
const response = await api.get(url);
setData(response as InvitationValidationResponse);
} catch (err: unknown) {
@@ -64,7 +64,7 @@ export function useCompleteInvitation() {
setIsPending(true);
setError(null);
try {
const response = await api.post('/Invitation/complete', data);
const response = await api.post('/api/v1/Invitation/complete', data);
return response as InvitationCompleteResponse;
} catch (err: unknown) {
const error = err instanceof Error ? err : new Error(String(err));
+1 -1
View File
@@ -26,7 +26,7 @@ export function useSetup() {
setIsPending(true);
setError(null);
try {
const response = await api.post('/Setup', data);
const response = await api.post('/api/v1/Setup/owner', data);
return response as SetupResponse;
} catch (err) {
const error = err instanceof Error ? err : new Error(String(err));
+2 -2
View File
@@ -2,7 +2,7 @@ import { http, HttpResponse } from 'msw';
import { API_BASE } from '../auth/fixtures';
export const invitationHandlers = [
http.get(`${API_BASE}/Invitation/validate`, ({ request }) => {
http.get(`${API_BASE}/api/v1/Invitation/validate`, ({ request }) => {
const token = new URL(request.url).searchParams.get('token');
if (token === 'valid-token') {
@@ -25,7 +25,7 @@ export const invitationHandlers = [
);
}),
http.post(`${API_BASE}/Invitation/complete`, async ({ request }) => {
http.post(`${API_BASE}/api/v1/Invitation/complete`, async ({ request }) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const body = await request.json() as any;
+5 -5
View File
@@ -4,11 +4,11 @@ import { API_BASE } from '../auth/fixtures';
/** Setup status mock (used by public bootstrap guards). */
export const setupHandlers = [
http.get(`${API_BASE}/Setup/status`, () =>
http.get(`${API_BASE}/api/v1/Setup/status`, () =>
HttpResponse.json<SetupStatus>({ initialized: true }),
),
http.post(`${API_BASE}/Setup`, async ({ request }) => {
http.post(`${API_BASE}/api/v1/Setup/owner`, async ({ request }) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const body = await request.json() as any;
if (!body.email || !body.password || !body.name) {
@@ -39,15 +39,15 @@ export const setupHandlers = [
/** Override: returns not-initialized status — use in tests for InitGuard. */
export const setupUninitializedHandlers = [
http.get(`${API_BASE}/Setup/status`, () =>
http.get(`${API_BASE}/api/v1/Setup/status`, () =>
HttpResponse.json<SetupStatus>({ initialized: false }),
),
];
/** Override: POST /Setup returns 409 — system already initialized. */
/** Override: POST /Setup/owner returns 409 — system already initialized. */
export const setupConflictHandlers = [
...setupUninitializedHandlers,
http.post(`${API_BASE}/Setup`, () =>
http.post(`${API_BASE}/api/v1/Setup/owner`, () =>
HttpResponse.json(
{ title: 'Conflict', detail: 'System has already been initialized.', status: 409 },
{ status: 409 },
+4 -2
View File
@@ -29,8 +29,10 @@ async function fetchSetupStatus(): Promise<SetupStatus> {
if (_setupStatusCache !== null) return _setupStatusCache;
if (_setupStatusPromise === null) {
_setupStatusPromise = api
.get<SetupStatus>('/Setup/status')
.then((s) => {
.get<{ initialized?: boolean; Initialized?: boolean }>('/api/v1/Setup/status')
.then((raw) => {
// Backend may return PascalCase (Initialized) if no camelCase policy is set.
const s: SetupStatus = { initialized: raw.initialized ?? raw.Initialized ?? false };
_setupStatusCache = s;
return s;
})
+1 -2
View File
@@ -1,10 +1,9 @@
import { beforeEach, describe, expect, it } from 'vitest';
import { screen } from '@testing-library/react';
import { renderApp, renderWithProviders, mockAuthenticated, mockGuest } from '@/test/utils';
import { renderApp, mockAuthenticated, mockGuest } from '@/test/utils';
import { server } from '@/mocks/server';
import { setupUninitializedHandlers } from '@/mocks/index';
import { _resetSetupStatusCache } from '@/router';
import { RoleGuard } from '@/components/auth/RoleGuard';
beforeEach(() => {
_resetSetupStatusCache();