Files
ss-tools/frontend/tests/maintenance-form.test.ts

181 lines
7.2 KiB
TypeScript

// #region MaintenanceFormTests [C:2] [TYPE Module] [SEMANTICS test, component, maintenance, start, form, environment_id]
// @BRIEF Component tests for StartMaintenanceForm — verifies environment_id is included in submission.
// @RELATION BINDS_TO -> [StartMaintenanceForm]
// @TEST_CONTRACT: Submit includes environment_id from environmentContextStore
// @TEST_EDGE: No environment selected -> error toast, no API call
// @TEST_EDGE: Environment selected -> API call includes environment_id
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/svelte';
// ── Hoisted mutable state (before vi.mock calls, hoisted to top) ──
const { mockStartMaintenance, mockAddToast, mockEnvStore } = vi.hoisted(() => {
// Mutable env store state — tests can change this before render
let _selectedEnvId = 'env-prod';
const mockEnvStoreObj = {
value: {
get selectedEnvId() { return _selectedEnvId; },
},
subscribe: vi.fn(),
};
return {
mockStartMaintenance: vi.fn(),
mockAddToast: vi.fn(),
mockEnvStore: {
obj: mockEnvStoreObj,
setSelectedEnvId(id: string) { _selectedEnvId = id; },
},
};
});
// ── Module mocks ──────────────────────────────────────────────
vi.mock('$lib/api/maintenance.js', () => ({
startMaintenance: mockStartMaintenance,
}));
vi.mock('$lib/toasts.svelte.js', () => ({
addToast: mockAddToast,
}));
vi.mock('$lib/stores/environmentContext.svelte.js', () => ({
environmentContextStore: mockEnvStore.obj,
}));
vi.mock('$lib/i18n/index.svelte.js', () => {
const subscribers = new Set();
const tData = {
maintenance: {
start_maintenance: 'Start Maintenance',
start_maintenance_description: 'Schedule a maintenance window',
start_now: 'Start Maintenance',
starting: 'Starting...',
start_success: 'Maintenance started. Task: {task_id}',
start_error: 'Failed to start maintenance',
table_name: 'Table Name',
table_required: 'Table name is required',
table_name_placeholder: 'e.g. raw.sales',
table_help: 'Enter the table name',
start_time_label: 'Start Time',
start_time_help: 'When maintenance begins',
end_time_label: 'End Time (optional)',
end_time_help: 'When maintenance ends',
message_label: 'Message (optional)',
message_placeholder: 'Scheduled data mart refresh',
template_label: 'Quick Template',
template_none: 'Custom',
template_daily_refresh: 'Daily Refresh',
template_daily_refresh_desc: '4h ETL window',
template_schema_migration: 'Schema Migration',
template_schema_migration_desc: '2h schema change',
template_emergency: 'Emergency',
template_emergency_desc: '1h critical fix',
template_extended: 'Extended',
template_extended_desc: '8h large refresh',
no_environment: 'No environment selected. Please select an environment first.',
},
common: {},
};
return {
t: {
subscribe(run: (_value: unknown) => void) {
subscribers.add(run);
run(tData);
return () => subscribers.delete(run);
},
},
_: (key: string) => key,
getT: () => ({}),
};
});
// ── Imports (after mocks) ─────────────────────────────────────
import StartMaintenanceForm from '$lib/components/StartMaintenanceForm.svelte';
describe('StartMaintenanceForm', () => {
beforeEach(() => {
vi.clearAllMocks();
// Reset to default: environment selected
mockEnvStore.setSelectedEnvId('env-prod');
});
// #region test_submits_with_environment_id [C:2] [TYPE Function]
// @BRIEF Submit with environment selected includes environment_id in API call.
// @TEST_EDGE: Environment selected -> API call includes environment_id
it('submits with environment_id from context store', async () => {
mockStartMaintenance.mockResolvedValue({
task_id: 'task-1',
maintenance_id: 'm-1',
status: 'pending',
});
render(StartMaintenanceForm);
// Fill in table name
const tableInput = screen.getByPlaceholderText('e.g. raw.sales');
await fireEvent.input(tableInput, { target: { value: 'raw.my_table' } });
// Click the submit button (use role to avoid heading match)
const submitButton = screen.getByRole('button', { name: /Start Maintenance/i });
await fireEvent.click(submitButton);
// Verify startMaintenance was called with environment_id
expect(mockStartMaintenance).toHaveBeenCalledTimes(1);
const callParams = mockStartMaintenance.mock.calls[0][0];
expect(callParams).toHaveProperty('environment_id', 'env-prod');
expect(callParams).toHaveProperty('tables');
expect(callParams.tables).toContain('raw.my_table');
});
// #endregion test_submits_with_environment_id
// #region test_validation_fails_without_environment [C:2] [TYPE Function]
// @BRIEF No environment selected shows error toast and does not call API.
// @TEST_EDGE: No environment selected -> error toast, no API call
it('shows error toast when no environment is selected', async () => {
// Set empty environment
mockEnvStore.setSelectedEnvId('');
render(StartMaintenanceForm);
// Fill in table name
const tableInput = screen.getByPlaceholderText('e.g. raw.sales');
await fireEvent.input(tableInput, { target: { value: 'raw.my_table' } });
// Click the submit button
const submitButton = screen.getByRole('button', { name: /Start Maintenance/i });
await fireEvent.click(submitButton);
// API should NOT have been called
expect(mockStartMaintenance).not.toHaveBeenCalled();
// Error toast should have been shown with the missing environment message
expect(mockAddToast).toHaveBeenCalledWith(
expect.stringContaining('No environment selected'),
'error',
);
});
// #endregion test_validation_fails_without_environment
// #region test_validation_fails_without_table [C:2] [TYPE Function]
// @BRIEF Empty table name shows inline validation, no API call.
it('shows validation error when table name is empty', async () => {
render(StartMaintenanceForm);
// Click Submit without filling table name
const submitButton = screen.getByRole('button', { name: /Start Maintenance/i });
await fireEvent.click(submitButton);
// API should NOT have been called
expect(mockStartMaintenance).not.toHaveBeenCalled();
// Inline validation error should be visible
expect(screen.getByText('Table name is required')).toBeTruthy();
});
// #endregion test_validation_fails_without_table
});
// #endregion MaintenanceFormTests