feat(12-05): drop DB-vs-env aside, add read-only DB-name field (gaps 1, 3)
- Remove the 'written to the database — not your environment file' aside from the Instance step intro - Render a read-only, disabled DB-name field under App URL, populated from GET /api/setup/status dbName - Helper text explains DB is configured via Docker env; only dbName is surfaced (T-12-3DB) - Tests: assert aside absent, DB field readOnly/disabled with mocked dbName, existing DB validation row intact
This commit is contained in:
@@ -210,9 +210,14 @@ describe('SetupPage — Already Locked screen', () => {
|
|||||||
describe('SetupPage — Step 2 VAPID validation (CR-01 gap)', () => {
|
describe('SetupPage — Step 2 VAPID validation (CR-01 gap)', () => {
|
||||||
let queryClient: QueryClient;
|
let queryClient: QueryClient;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
queryClient = makeQueryClient();
|
queryClient = makeQueryClient();
|
||||||
vi.resetAllMocks();
|
vi.resetAllMocks();
|
||||||
|
const { fetchSetupStatus } = await import('../api/client.js');
|
||||||
|
(fetchSetupStatus as ReturnType<typeof vi.fn>).mockResolvedValue({
|
||||||
|
setupComplete: false,
|
||||||
|
dbName: 'familysync',
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -305,3 +310,66 @@ describe('SetupPage — Step 2 VAPID validation (CR-01 gap)', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ── Tests: Step 2 Instance copy + read-only DB-name field (gaps 1, 3-frontend) ──
|
||||||
|
|
||||||
|
describe('SetupPage — Step 2 Instance copy + DB-name field (gaps 1, 3)', () => {
|
||||||
|
let queryClient: QueryClient;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
queryClient = makeQueryClient();
|
||||||
|
vi.resetAllMocks();
|
||||||
|
const { fetchSetupStatus } = await import('../api/client.js');
|
||||||
|
(fetchSetupStatus as ReturnType<typeof vi.fn>).mockResolvedValue({
|
||||||
|
setupComplete: false,
|
||||||
|
dbName: 'familysync',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
async function advanceToStep2() {
|
||||||
|
renderSetupPage(queryClient);
|
||||||
|
const continueBtn = await screen.findByRole('button', { name: 'Continue' });
|
||||||
|
fireEvent.click(continueBtn);
|
||||||
|
await screen.findByText('Instance Configuration');
|
||||||
|
}
|
||||||
|
|
||||||
|
it('Instance step intro no longer contains the DB-vs-env-file aside (gap 1)', async () => {
|
||||||
|
await advanceToStep2();
|
||||||
|
expect(screen.queryByText(/not your environment file/i)).toBeNull();
|
||||||
|
// First sentence is preserved
|
||||||
|
expect(screen.getByText(/Enter your instance/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders a read-only, disabled DB-name field populated from status dbName (gap 3)', async () => {
|
||||||
|
await advanceToStep2();
|
||||||
|
const dbField = await screen.findByLabelText('Database');
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(dbField).toHaveValue('familysync');
|
||||||
|
});
|
||||||
|
expect(dbField).toHaveAttribute('readonly');
|
||||||
|
expect(dbField).toBeDisabled();
|
||||||
|
expect(dbField).toHaveAttribute('aria-readonly', 'true');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps the existing "Database connection verified" validation row available', async () => {
|
||||||
|
const { postSetupConfig, validateSetupDb, validateSetupOidc, validateSetupVapid } =
|
||||||
|
await import('../api/client.js');
|
||||||
|
(postSetupConfig as ReturnType<typeof vi.fn>).mockResolvedValue(undefined);
|
||||||
|
(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();
|
||||||
|
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' }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText('Database connection verified.')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// (gap-4 Back-navigation tests added in Task 2)
|
||||||
|
|||||||
@@ -21,9 +21,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState, useRef, useEffect } from 'react';
|
import { useState, useRef, useEffect } from 'react';
|
||||||
import { useMutation } from '@tanstack/react-query';
|
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||||
import { ShieldCheck, CheckCircle, AlertCircle, Loader2 } from 'lucide-react';
|
import { ShieldCheck, CheckCircle, AlertCircle, Loader2 } from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
|
fetchSetupStatus,
|
||||||
postSetupConfig,
|
postSetupConfig,
|
||||||
validateSetupDb,
|
validateSetupDb,
|
||||||
validateSetupOidc,
|
validateSetupOidc,
|
||||||
@@ -442,6 +443,17 @@ function Step2Config({ onBack, onSuccess, stepHeadingRef }: Step2Props) {
|
|||||||
const [vapidPublicKey, setVapidPublicKey] = useState('');
|
const [vapidPublicKey, setVapidPublicKey] = useState('');
|
||||||
const [fieldError, setFieldError] = useState<string | null>(null);
|
const [fieldError, setFieldError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
// Gap 3 (frontend): fetch the env-derived, non-secret DB name so the
|
||||||
|
// "database connection verified" row below has an on-screen referent.
|
||||||
|
// Only dbName is surfaced — DB_HOST/DB_USER/DB_PASSWORD are never fetched (T-12-3DB).
|
||||||
|
const { data: setupStatus } = useQuery({
|
||||||
|
queryKey: ['setupStatus'],
|
||||||
|
queryFn: fetchSetupStatus,
|
||||||
|
staleTime: 0,
|
||||||
|
retry: false,
|
||||||
|
});
|
||||||
|
const dbName = setupStatus?.dbName ?? '';
|
||||||
|
|
||||||
const [validationRows, setValidationRows] = useState<Pick<ValidationRowStatus, 'db' | 'oidc' | 'vapid'>>({
|
const [validationRows, setValidationRows] = useState<Pick<ValidationRowStatus, 'db' | 'oidc' | 'vapid'>>({
|
||||||
db: 'idle',
|
db: 'idle',
|
||||||
oidc: 'idle',
|
oidc: 'idle',
|
||||||
@@ -553,8 +565,7 @@ function Step2Config({ onBack, onSuccess, stepHeadingRef }: Step2Props) {
|
|||||||
lineHeight: 1.5,
|
lineHeight: 1.5,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Enter your instance's connection details. These are written to the database — not
|
Enter your instance's connection details.
|
||||||
your environment file.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* App URL */}
|
{/* App URL */}
|
||||||
@@ -574,6 +585,32 @@ function Step2Config({ onBack, onSuccess, stepHeadingRef }: Step2Props) {
|
|||||||
<div style={helperStyle}>The public URL where FamilySync is reachable.</div>
|
<div style={helperStyle}>The public URL where FamilySync is reachable.</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Database (read-only, env-derived) — gives the DB validation row below a referent */}
|
||||||
|
<div style={{ marginBottom: 'var(--space-4, 16px)' }}>
|
||||||
|
<label htmlFor="setup-db-name" style={labelStyle}>
|
||||||
|
Database
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="setup-db-name"
|
||||||
|
type="text"
|
||||||
|
value={dbName || '—'}
|
||||||
|
readOnly
|
||||||
|
disabled
|
||||||
|
aria-readonly="true"
|
||||||
|
tabIndex={-1}
|
||||||
|
style={{
|
||||||
|
...inputStyle(false),
|
||||||
|
background: 'var(--color-surface-dim, #f7f7f8)',
|
||||||
|
color: 'var(--color-text-secondary, #6b7280)',
|
||||||
|
cursor: 'default',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div style={helperStyle}>
|
||||||
|
Configured via the server's Docker environment (<code>DB_HOST</code>,{' '}
|
||||||
|
<code>DB_PORT</code>, <code>DB_USER</code>, <code>DB_PASSWORD</code>) — not entered here.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* OIDC Issuer */}
|
{/* OIDC Issuer */}
|
||||||
<div style={{ marginBottom: 'var(--space-4, 16px)' }}>
|
<div style={{ marginBottom: 'var(--space-4, 16px)' }}>
|
||||||
<label htmlFor="setup-oidc-issuer" style={labelStyle}>
|
<label htmlFor="setup-oidc-issuer" style={labelStyle}>
|
||||||
|
|||||||
Reference in New Issue
Block a user