3081d99355
Wave 7. Mirrors the existing Google ID-token-exchange pattern (Program.cs smart-scheme dispatch, JWT bearer scheme, AuthController exchange/link/ unlink endpoints, ApplicationUser fields, reconciler columns) for Microsoft Entra ID + personal accounts via the multi-tenant "common" endpoint. Google/Microsoft sign-in previously only worked for accounts already linked to an existing local user -- there was no way to actually sign up via OAuth. Both exchange endpoints now create a new user when no match is found and Auth:AllowRegistration is true, same gate as email/password registration. Frontend: new MicrosoftAuthCard (MSAL popup flow -- Microsoft has no vanilla-JS equivalent to Google's Identity Services script) wired into the login page's provider tabs and the profile page's account-linking section. REACT_APP_MICROSOFT_CLIENT_ID env var, Auth:MicrosoftClientId config gate on the backend.
67 lines
2.8 KiB
TypeScript
67 lines
2.8 KiB
TypeScript
import React from 'react';
|
|
import { configure } from '@testing-library/react';
|
|
|
|
// Heavy MUI views (job table, workspace dialog, profile page) can exceed the
|
|
// 1s default async query timeout on slower machines; findBy*/waitFor assertions
|
|
// still resolve as soon as the element appears.
|
|
configure({ asyncUtilTimeout: 4000 });
|
|
jest.setTimeout(30000);
|
|
|
|
jest.mock('./api', () => ({
|
|
api: {
|
|
get: jest.fn(),
|
|
post: jest.fn(() => Promise.resolve({ data: {} })),
|
|
put: jest.fn(() => Promise.resolve({ data: {} })),
|
|
patch: jest.fn(() => Promise.resolve({ data: {} })),
|
|
delete: jest.fn(() => Promise.resolve({ data: {} })),
|
|
interceptors: { request: { use: jest.fn() }, response: { use: jest.fn() } },
|
|
},
|
|
getApiErrorMessage: jest.fn((error: any, fallback?: string) => {
|
|
const text = typeof error?.response?.data === 'string' && error.response.data.trim()
|
|
? error.response.data.trim()
|
|
: typeof error?.message === 'string' && error.message.trim()
|
|
? error.message.trim()
|
|
: '';
|
|
if (!text) return fallback || 'Request failed.';
|
|
if (/<\s*html\b|<\s*body\b|<\s*head\b|<\s*title\b|<\s*!doctype\b/i.test(text)) return fallback || 'Request failed.';
|
|
return text.length > 300 ? `${text.slice(0, 297).trimEnd()}...` : text;
|
|
}),
|
|
}));
|
|
|
|
jest.mock('./components/GoogleAuthCard', () => () => null);
|
|
jest.mock('./components/MicrosoftAuthCard', () => () => null);
|
|
|
|
beforeEach(() => {
|
|
const { api } = require('./api');
|
|
api.get.mockImplementation((url: string) => {
|
|
if (url === '/auth/config') {
|
|
return Promise.resolve({ data: { requireAuth: false, googleEnabled: false, microsoftEnabled: false, localEnabled: true, allowRegistration: false } });
|
|
}
|
|
if (url === '/auth/me') {
|
|
return Promise.resolve({ data: { roles: [], email: 'demo@example.com', userName: 'demo' } });
|
|
}
|
|
if (url === '/jobapplications/reminders') {
|
|
return Promise.resolve({ data: [] });
|
|
}
|
|
if (url === '/companies') {
|
|
return Promise.resolve({ data: [] });
|
|
}
|
|
if (url === '/jobapplications') {
|
|
return Promise.resolve({ data: { items: [], total: 0, page: 1, pageSize: 15 } });
|
|
}
|
|
if (url === '/jobapplications/stats') {
|
|
return Promise.resolve({ data: { total: 0, active: 0, deleted: 0, byStatus: {}, appliedLast30Days: 0, averageDaysSinceApplied: 0 } });
|
|
}
|
|
if (url === '/jobapplications/analytics-overview') {
|
|
return Promise.resolve({ data: { funnel: [], responseRateBySource: [], topCompanies: [], totalResponses: 0, totalActive: 0 } });
|
|
}
|
|
if (url === '/jobapplications/analytics' || url === '/jobapplications/tags') {
|
|
return Promise.resolve({ data: [] });
|
|
}
|
|
if (url === '/jobapplications/tag-trends') {
|
|
return Promise.resolve({ data: { months: [], series: [] } });
|
|
}
|
|
return Promise.resolve({ data: [] });
|
|
});
|
|
});
|