chore: commit remaining maintenance and model changes
This commit is contained in:
@@ -16,6 +16,28 @@ describe('MaintenanceApi URL construction', () => {
|
||||
expect(mockRequestApi).toHaveBeenCalledWith('/maintenance/start', 'POST', expect.any(Object));
|
||||
});
|
||||
|
||||
it('startMaintenance → forwards environment_id in params', async () => {
|
||||
mockRequestApi.mockResolvedValue({ task_id: 't-1', maintenance_id: 'm-1', status: 'pending' });
|
||||
await startMaintenance({
|
||||
environment_id: 'env-prod',
|
||||
tables: ['raw.sales'],
|
||||
start_time: '2026-05-21T22:00:00Z',
|
||||
});
|
||||
expect(mockRequestApi).toHaveBeenCalledWith(
|
||||
'/maintenance/start',
|
||||
'POST',
|
||||
expect.objectContaining({ environment_id: 'env-prod' }),
|
||||
);
|
||||
});
|
||||
|
||||
it('startMaintenance → rejects without environment_id (backend returns 422)', async () => {
|
||||
mockRequestApi.mockRejectedValue({ status: 422, message: 'Field required' });
|
||||
await expect(startMaintenance({
|
||||
tables: ['raw.sales'],
|
||||
start_time: '2026-05-21T22:00:00Z',
|
||||
})).rejects.toBeTruthy();
|
||||
});
|
||||
|
||||
it('endMaintenance → POST /maintenance/{id}/end', async () => {
|
||||
mockRequestApi.mockResolvedValue({ task_id: 't-1', status: 'pending' });
|
||||
await endMaintenance('ev-abc');
|
||||
|
||||
180
frontend/tests/maintenance-form.test.ts
Normal file
180
frontend/tests/maintenance-form.test.ts
Normal file
@@ -0,0 +1,180 @@
|
||||
// #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
|
||||
Reference in New Issue
Block a user