feat: complete release readiness work

- consolidate API ownership and remove dead vendor code

- add Stripe billing, learning paths, and public CV hardening

- add migration, recovery, security, audit, and browser gates
This commit is contained in:
cesnimda
2026-07-31 16:54:16 +02:00
parent a23c3dfc97
commit ce76046a29
1634 changed files with 6889 additions and 135429 deletions
+28 -12
View File
@@ -1,6 +1,6 @@
import React from 'react';
import '@testing-library/jest-dom';
import { render, screen, waitFor } from '@testing-library/react';
import { act, render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { MemoryRouter } from 'react-router-dom';
@@ -20,9 +20,9 @@ const mockedApi = api as jest.Mocked<typeof api>;
let consoleErrorSpy: jest.SpyInstance;
function renderLoginPage(initialMode: "login" | "register" = "login") {
function renderLoginPage(initialMode: "login" | "register" = "login", from?: string) {
return render(
<MemoryRouter future={{ v7_startTransition: true, v7_relativeSplatPath: true }}>
<MemoryRouter initialEntries={[{ pathname: "/login", state: from ? { from } : undefined }]} future={{ v7_startTransition: true, v7_relativeSplatPath: true }}>
<I18nProvider>
<ToastProvider>
<LoginPage initialMode={initialMode} />
@@ -32,6 +32,10 @@ function renderLoginPage(initialMode: "login" | "register" = "login") {
);
}
async function click(element: Element) {
await act(async () => { userEvent.click(element); });
}
describe('LoginPage', () => {
it('renders a dedicated registration screen and explains when registration is disabled', async () => {
mockedApi.get.mockResolvedValueOnce({
@@ -75,8 +79,8 @@ describe('LoginPage', () => {
await userEvent.type(screen.getByLabelText('Email'), 'person@example.com');
await userEvent.type(screen.getByLabelText('Current password'), 'hunter2');
await userEvent.click(screen.getByLabelText('Remember me'));
await userEvent.click(screen.getByRole('button', { name: 'Sign in' }));
await click(screen.getByLabelText('Remember me'));
await click(screen.getByRole('button', { name: 'Sign in' }));
await waitFor(() => expect(mockedApi.post).toHaveBeenCalledWith('/auth/login', { email: 'person@example.com', password: 'hunter2', rememberMe: false }));
await waitFor(() => expect(mockedApi.get).toHaveBeenCalledWith('/auth/me'));
@@ -86,12 +90,24 @@ describe('LoginPage', () => {
expect(window.localStorage.getItem('authTokenPersistence')).toBe('session');
});
it('rejects an unsafe post-login redirect path', async () => {
mockedApi.post.mockResolvedValueOnce({ data: { authenticated: true, provider: 'local' } } as any);
mockedApi.get.mockResolvedValueOnce({ data: { roles: [], email: 'person@example.com', userName: 'person' } } as any);
renderLoginPage("login", "/\\evil.example");
await userEvent.type(await screen.findByLabelText('Email'), 'person@example.com');
await userEvent.type(screen.getByLabelText('Current password'), 'hunter2');
await click(screen.getByRole('button', { name: 'Sign in' }));
await waitFor(() => expect(mockNavigate).toHaveBeenCalledWith('/dashboard', { replace: true }));
});
it('opens the separate forgot-password page with the typed email prefilled', async () => {
renderLoginPage();
await screen.findByLabelText('Email');
await userEvent.type(screen.getByLabelText('Email'), 'person@example.com');
await userEvent.click(screen.getByRole('button', { name: 'Forgot password?' }));
await click(screen.getByRole('button', { name: 'Forgot password?' }));
expect(mockNavigate).toHaveBeenCalledWith('/forgot-password?email=person%40example.com');
});
@@ -114,13 +130,13 @@ describe('LoginPage', () => {
await userEvent.type(screen.getByLabelText('Email'), 'person@example.com');
await userEvent.type(screen.getByLabelText('Current password'), 'hunter2');
await userEvent.click(screen.getByRole('button', { name: 'Sign in' }));
await click(screen.getByRole('button', { name: 'Sign in' }));
await screen.findByText('Two-factor verification');
expect(screen.queryByLabelText('Email')).not.toBeInTheDocument();
await userEvent.type(screen.getByLabelText('Code'), '123456');
await userEvent.click(screen.getByRole('button', { name: 'Verify' }));
await click(screen.getByRole('button', { name: 'Verify' }));
await waitFor(() => expect(mockedApi.post).toHaveBeenCalledWith('/auth/2fa/challenge', { pendingToken: 'pending-abc', code: '123456', trustDevice: false }));
await waitFor(() => expect(mockedApi.get).toHaveBeenCalledWith('/auth/me'));
@@ -142,11 +158,11 @@ describe('LoginPage', () => {
await screen.findByLabelText('Email');
await userEvent.type(screen.getByLabelText('Email'), 'person@example.com');
await userEvent.type(screen.getByLabelText('Current password'), 'hunter2');
await userEvent.click(screen.getByRole('button', { name: 'Sign in' }));
await click(screen.getByRole('button', { name: 'Sign in' }));
await screen.findByText('Two-factor verification');
await userEvent.type(screen.getByLabelText('Code'), '123456');
await userEvent.click(screen.getByRole('button', { name: 'Verify' }));
await click(screen.getByRole('button', { name: 'Verify' }));
expect(await screen.findByRole('alert')).toHaveTextContent('Too many attempts. Please wait a few minutes and try again.');
});
@@ -170,11 +186,11 @@ describe('LoginPage', () => {
await userEvent.type(screen.getByLabelText('Email'), 'unverified@example.com');
await userEvent.type(screen.getByLabelText('Current password'), 'hunter2');
await userEvent.click(screen.getByRole('button', { name: 'Sign in' }));
await click(screen.getByRole('button', { name: 'Sign in' }));
expect(await screen.findByText('Please verify your email address before signing in.')).toBeInTheDocument();
await userEvent.click(screen.getByRole('button', { name: 'Resend verification email' }));
await click(screen.getByRole('button', { name: 'Resend verification email' }));
await waitFor(() => expect(mockedApi.post).toHaveBeenCalledWith('/auth/resend-verification-email', { email: 'unverified@example.com' }));
await screen.findByText('Verification email sent. Check your inbox.');