test(12-04): RED — wizard must validate VAPID before proceeding (SETUP-02 gap)
- Add 4 VAPID validation tests to SetupPage.test.tsx (CR-01 gap closure) - Tests assert: validateSetupVapid is called, VAPID row renders, Continue is blocked when VAPID fails, Continue appears only after all 3 pass - 3 tests currently FAIL (RED) — current code lacks validateSetupVapid import and has no vapid ValidationRow or vapid gate on bothPassed Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
523742c489
commit
7d0205df05
@@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
import { render, screen, waitFor } from '@testing-library/react';
|
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
|
||||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
import { MemoryRouter } from 'react-router';
|
import { MemoryRouter } from 'react-router';
|
||||||
import { SetupPage } from './SetupPage.js';
|
import { SetupPage } from './SetupPage.js';
|
||||||
@@ -199,3 +199,109 @@ describe('SetupPage — Already Locked screen', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ── Tests: Step 2 VAPID validation (CR-01 / SETUP-02 gap closure) ────────────
|
||||||
|
//
|
||||||
|
// RED gate: these tests must FAIL against the current SetupPage.tsx because:
|
||||||
|
// - validateSetupVapid is not imported in SetupPage.tsx
|
||||||
|
// - No VAPID ValidationRow is rendered
|
||||||
|
// - bothPassed is gated on db+oidc only (not vapid)
|
||||||
|
|
||||||
|
describe('SetupPage — Step 2 VAPID validation (CR-01 gap)', () => {
|
||||||
|
let queryClient: QueryClient;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
queryClient = makeQueryClient();
|
||||||
|
vi.resetAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper: advance from Welcome (step 1) to Instance Configuration (step 2).
|
||||||
|
*/
|
||||||
|
async function advanceToStep2() {
|
||||||
|
renderSetupPage(queryClient);
|
||||||
|
const continueBtn = await screen.findByRole('button', { name: 'Continue' });
|
||||||
|
fireEvent.click(continueBtn);
|
||||||
|
// Step 2 heading appears
|
||||||
|
await screen.findByText('Instance Configuration');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper: fill all 4 fields in step 2 and click "Save & Validate".
|
||||||
|
* Mocks must be set up by the caller.
|
||||||
|
*/
|
||||||
|
async function fillAndSubmitStep2() {
|
||||||
|
const { postSetupConfig } = await import('../api/client.js');
|
||||||
|
(postSetupConfig as ReturnType<typeof vi.fn>).mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
fireEvent.change(screen.getByLabelText('App URL'), { target: { value: 'https://app.example.com' } });
|
||||||
|
fireEvent.change(screen.getByLabelText('OIDC issuer URL'), { target: { value: 'https://auth.example.com' } });
|
||||||
|
fireEvent.change(screen.getByLabelText('OIDC client ID'), { target: { value: 'familysync' } });
|
||||||
|
fireEvent.change(screen.getByLabelText('VAPID public key'), { target: { value: 'BHtest123' } });
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Save & Validate' }));
|
||||||
|
}
|
||||||
|
|
||||||
|
it('calls validateSetupVapid after DB and OIDC pass in step 2', async () => {
|
||||||
|
const { validateSetupDb, validateSetupOidc, validateSetupVapid } = await import('../api/client.js');
|
||||||
|
(validateSetupDb as ReturnType<typeof vi.fn>).mockResolvedValue(undefined);
|
||||||
|
(validateSetupOidc as ReturnType<typeof vi.fn>).mockResolvedValue(undefined);
|
||||||
|
(validateSetupVapid as ReturnType<typeof vi.fn>).mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
await advanceToStep2();
|
||||||
|
await fillAndSubmitStep2();
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(validateSetupVapid as ReturnType<typeof vi.fn>).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders a VAPID validation row after step 2 validation completes', async () => {
|
||||||
|
const { validateSetupDb, validateSetupOidc, validateSetupVapid } = await import('../api/client.js');
|
||||||
|
(validateSetupDb as ReturnType<typeof vi.fn>).mockResolvedValue(undefined);
|
||||||
|
(validateSetupOidc as ReturnType<typeof vi.fn>).mockResolvedValue(undefined);
|
||||||
|
(validateSetupVapid as ReturnType<typeof vi.fn>).mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
await advanceToStep2();
|
||||||
|
await fillAndSubmitStep2();
|
||||||
|
|
||||||
|
// After all three resolve, the VAPID success text should appear in the ValidationRow
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText(/VAPID keys verified/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does NOT show Continue when VAPID validation fails', async () => {
|
||||||
|
const { validateSetupDb, validateSetupOidc, validateSetupVapid } = await import('../api/client.js');
|
||||||
|
(validateSetupDb as ReturnType<typeof vi.fn>).mockResolvedValue(undefined);
|
||||||
|
(validateSetupOidc as ReturnType<typeof vi.fn>).mockResolvedValue(undefined);
|
||||||
|
(validateSetupVapid as ReturnType<typeof vi.fn>).mockRejectedValue(
|
||||||
|
new Error('VAPID validation failed. Check that your VAPID keys were generated with `npm run generate-secrets`.')
|
||||||
|
);
|
||||||
|
|
||||||
|
await advanceToStep2();
|
||||||
|
await fillAndSubmitStep2();
|
||||||
|
|
||||||
|
// Wait for the VAPID failure to land in the UI
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText(/VAPID validation failed/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Continue button must NOT be present when VAPID fails
|
||||||
|
expect(screen.queryByRole('button', { name: 'Continue' })).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows Continue only when db, oidc, AND vapid all pass', async () => {
|
||||||
|
const { validateSetupDb, validateSetupOidc, validateSetupVapid } = await import('../api/client.js');
|
||||||
|
(validateSetupDb as ReturnType<typeof vi.fn>).mockResolvedValue(undefined);
|
||||||
|
(validateSetupOidc as ReturnType<typeof vi.fn>).mockResolvedValue(undefined);
|
||||||
|
(validateSetupVapid as ReturnType<typeof vi.fn>).mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
await advanceToStep2();
|
||||||
|
await fillAndSubmitStep2();
|
||||||
|
|
||||||
|
// Continue appears only after all three validations pass
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByRole('button', { name: 'Continue' })).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user