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:
@@ -35,7 +35,7 @@ export function useValidateInvitation(token: string | null) {
|
|||||||
setIsPending(true);
|
setIsPending(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
try {
|
try {
|
||||||
const url = `/Invitation/validate?token=${encodeURIComponent(token)}`;
|
const url = `/api/v1/Invitation/validate?token=${encodeURIComponent(token)}`;
|
||||||
const response = await api.get(url);
|
const response = await api.get(url);
|
||||||
setData(response as InvitationValidationResponse);
|
setData(response as InvitationValidationResponse);
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
@@ -64,7 +64,7 @@ export function useCompleteInvitation() {
|
|||||||
setIsPending(true);
|
setIsPending(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
try {
|
try {
|
||||||
const response = await api.post('/Invitation/complete', data);
|
const response = await api.post('/api/v1/Invitation/complete', data);
|
||||||
return response as InvitationCompleteResponse;
|
return response as InvitationCompleteResponse;
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
const error = err instanceof Error ? err : new Error(String(err));
|
const error = err instanceof Error ? err : new Error(String(err));
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export function useSetup() {
|
|||||||
setIsPending(true);
|
setIsPending(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
try {
|
try {
|
||||||
const response = await api.post('/Setup', data);
|
const response = await api.post('/api/v1/Setup/owner', data);
|
||||||
return response as SetupResponse;
|
return response as SetupResponse;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const error = err instanceof Error ? err : new Error(String(err));
|
const error = err instanceof Error ? err : new Error(String(err));
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { http, HttpResponse } from 'msw';
|
|||||||
import { API_BASE } from '../auth/fixtures';
|
import { API_BASE } from '../auth/fixtures';
|
||||||
|
|
||||||
export const invitationHandlers = [
|
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');
|
const token = new URL(request.url).searchParams.get('token');
|
||||||
|
|
||||||
if (token === 'valid-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
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const body = await request.json() as any;
|
const body = await request.json() as any;
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ import { API_BASE } from '../auth/fixtures';
|
|||||||
|
|
||||||
/** Setup status mock (used by public bootstrap guards). */
|
/** Setup status mock (used by public bootstrap guards). */
|
||||||
export const setupHandlers = [
|
export const setupHandlers = [
|
||||||
http.get(`${API_BASE}/Setup/status`, () =>
|
http.get(`${API_BASE}/api/v1/Setup/status`, () =>
|
||||||
HttpResponse.json<SetupStatus>({ initialized: true }),
|
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
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const body = await request.json() as any;
|
const body = await request.json() as any;
|
||||||
if (!body.email || !body.password || !body.name) {
|
if (!body.email || !body.password || !body.name) {
|
||||||
@@ -39,15 +39,15 @@ export const setupHandlers = [
|
|||||||
|
|
||||||
/** Override: returns not-initialized status — use in tests for InitGuard. */
|
/** Override: returns not-initialized status — use in tests for InitGuard. */
|
||||||
export const setupUninitializedHandlers = [
|
export const setupUninitializedHandlers = [
|
||||||
http.get(`${API_BASE}/Setup/status`, () =>
|
http.get(`${API_BASE}/api/v1/Setup/status`, () =>
|
||||||
HttpResponse.json<SetupStatus>({ initialized: false }),
|
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 = [
|
export const setupConflictHandlers = [
|
||||||
...setupUninitializedHandlers,
|
...setupUninitializedHandlers,
|
||||||
http.post(`${API_BASE}/Setup`, () =>
|
http.post(`${API_BASE}/api/v1/Setup/owner`, () =>
|
||||||
HttpResponse.json(
|
HttpResponse.json(
|
||||||
{ title: 'Conflict', detail: 'System has already been initialized.', status: 409 },
|
{ title: 'Conflict', detail: 'System has already been initialized.', status: 409 },
|
||||||
{ status: 409 },
|
{ status: 409 },
|
||||||
|
|||||||
@@ -29,8 +29,10 @@ async function fetchSetupStatus(): Promise<SetupStatus> {
|
|||||||
if (_setupStatusCache !== null) return _setupStatusCache;
|
if (_setupStatusCache !== null) return _setupStatusCache;
|
||||||
if (_setupStatusPromise === null) {
|
if (_setupStatusPromise === null) {
|
||||||
_setupStatusPromise = api
|
_setupStatusPromise = api
|
||||||
.get<SetupStatus>('/Setup/status')
|
.get<{ initialized?: boolean; Initialized?: boolean }>('/api/v1/Setup/status')
|
||||||
.then((s) => {
|
.then((raw) => {
|
||||||
|
// Backend may return PascalCase (Initialized) if no camelCase policy is set.
|
||||||
|
const s: SetupStatus = { initialized: raw.initialized ?? raw.Initialized ?? false };
|
||||||
_setupStatusCache = s;
|
_setupStatusCache = s;
|
||||||
return s;
|
return s;
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { beforeEach, describe, expect, it } from 'vitest';
|
import { beforeEach, describe, expect, it } from 'vitest';
|
||||||
import { screen } from '@testing-library/react';
|
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 { server } from '@/mocks/server';
|
||||||
import { setupUninitializedHandlers } from '@/mocks/index';
|
import { setupUninitializedHandlers } from '@/mocks/index';
|
||||||
import { _resetSetupStatusCache } from '@/router';
|
import { _resetSetupStatusCache } from '@/router';
|
||||||
import { RoleGuard } from '@/components/auth/RoleGuard';
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
_resetSetupStatusCache();
|
_resetSetupStatusCache();
|
||||||
|
|||||||
Reference in New Issue
Block a user