feat: Introduce and enforce test contract annotations for critical modules and update coverage tracking.

This commit is contained in:
2026-03-04 12:58:42 +03:00
parent 09e59ba88b
commit 7194f6a4c4
16 changed files with 15569 additions and 3336 deletions

View File

@@ -5,6 +5,13 @@
// @LAYER: UI (Tests)
// @RELATION: VERIFIES -> frontend/src/components/TaskLogViewer.svelte
// @INVARIANT: Duplicate logs are never appended. Polling only active for in-progress tasks.
// @TEST_CONTRACT: TaskLogViewerPropsAndLogStream -> RenderedLogTimeline
// @TEST_SCENARIO: historical_and_realtime_merge -> Historical logs render and realtime logs append without duplication.
// @TEST_FIXTURE: valid_viewer -> INLINE_JSON
// @TEST_EDGE: no_task_id -> Null taskId does not trigger fetch.
// @TEST_EDGE: fetch_failure -> Network failure renders recoverable error state with retry action.
// @TEST_EDGE: duplicate_realtime_entry -> Existing log is not duplicated when repeated in realtime stream.
// @TEST_INVARIANT: no_duplicate_log_rows -> VERIFIED_BY: [historical_and_realtime_merge, duplicate_realtime_entry]
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, waitFor } from '@testing-library/svelte';
@@ -15,6 +22,8 @@ vi.mock('../../services/taskService.js', () => ({
getTaskLogs: vi.fn()
}));
const getTaskLogsMock = vi.mocked(getTaskLogs);
vi.mock('../../lib/i18n', () => ({
t: {
subscribe: (fn) => {
@@ -39,13 +48,13 @@ describe('TaskLogViewer Component', () => {
});
it('renders loading state initially', () => {
getTaskLogs.mockResolvedValue([]);
getTaskLogsMock.mockResolvedValue([]);
render(TaskLogViewer, { inline: true, taskId: 'task-123' });
expect(screen.getByText('Loading...')).toBeDefined();
});
it('fetches and displays historical logs', async () => {
getTaskLogs.mockResolvedValue([
getTaskLogsMock.mockResolvedValue([
{ timestamp: '2024-01-01T00:00:00', level: 'INFO', message: 'Historical log entry' }
]);
@@ -59,7 +68,7 @@ describe('TaskLogViewer Component', () => {
});
it('displays error message on fetch failure', async () => {
getTaskLogs.mockRejectedValue(new Error('Network error fetching logs'));
getTaskLogsMock.mockRejectedValue(new Error('Network error fetching logs'));
render(TaskLogViewer, { inline: true, taskId: 'task-123' });
@@ -70,7 +79,7 @@ describe('TaskLogViewer Component', () => {
});
it('appends real-time logs passed as props', async () => {
getTaskLogs.mockResolvedValue([
getTaskLogsMock.mockResolvedValue([
{ timestamp: '2024-01-01T00:00:00', level: 'INFO', message: 'Historical log entry' }
]);
@@ -99,7 +108,7 @@ describe('TaskLogViewer Component', () => {
});
it('deduplicates real-time logs that are already in historical logs', async () => {
getTaskLogs.mockResolvedValue([
getTaskLogsMock.mockResolvedValue([
{ timestamp: '2024-01-01T00:00:00', level: 'INFO', message: 'Duplicate log entry' }
]);
@@ -132,7 +141,7 @@ describe('TaskLogViewer Component', () => {
// @TEST_FIXTURE valid_viewer
it('fetches and displays historical logs in modal mode under valid_viewer fixture', async () => {
getTaskLogs.mockResolvedValue([
getTaskLogsMock.mockResolvedValue([
{ timestamp: '2024-01-01T00:00:00', level: 'INFO', message: 'Modal log entry' }
]);

View File

@@ -5,6 +5,13 @@
// @LAYER: UI Tests
// @RELATION: VERIFIES -> frontend/src/lib/components/assistant/AssistantChatPanel.svelte
// @INVARIANT: Critical assistant UX states and action hooks remain present in component source.
// @TEST_CONTRACT: AssistantChatSourceArtifacts -> ContractAssertions
// @TEST_SCENARIO: assistant_contract_and_i18n_intact -> Component semantic/UX anchors and locale keys stay consistent.
// @TEST_FIXTURE: assistant_locales_en_ru -> file:src/lib/i18n/locales/en.json + file:src/lib/i18n/locales/ru.json
// @TEST_EDGE: missing_component_anchor -> Missing DEF/UX tags fails contract assertion.
// @TEST_EDGE: missing_action_hook -> Missing confirm/cancel/open_task hooks fails integration assertion.
// @TEST_EDGE: missing_locale_key -> Missing assistant locale key in en/ru fails dictionary assertion.
// @TEST_INVARIANT: assistant_ux_contract_visible -> VERIFIED_BY: [assistant_contract_and_i18n_intact]
import { describe, it, expect } from 'vitest';
import fs from 'node:fs';

View File

@@ -8,6 +8,13 @@
// @LAYER: UI
// @RELATION: VERIFIES -> ../ReportCard.svelte
// @INVARIANT: Each test asserts at least one observable UX contract outcome.
// @TEST_CONTRACT: ReportCardInputProps -> ObservableUXOutput
// @TEST_SCENARIO: ready_state_shows_summary_status_type -> Ready state renders summary/status/type labels.
// @TEST_FIXTURE: valid_report_card -> INLINE_JSON
// @TEST_EDGE: empty_report_object -> Missing fields use placeholders and fallback labels.
// @TEST_EDGE: random_status -> Unknown status is rendered without crashing.
// @TEST_EDGE: missing_optional_fields -> Partial report keeps component interactive and emits select.
// @TEST_INVARIANT: report_card_state_is_observable -> VERIFIED_BY: [ready_state_shows_summary_status_type, empty_report_object, random_status]
import { describe, it, expect, vi } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/svelte';
@@ -39,7 +46,7 @@ describe('ReportCard UX Contract', () => {
// @UX_STATE: Ready -> Card displays summary/status/type.
it('should display summary, status and type in Ready state', () => {
render(ReportCard, { report: mockReport });
render(ReportCard, { report: mockReport, onselect: vi.fn() });
expect(screen.getByText(mockReport.summary)).toBeDefined();
// mockReport.status is "success", getStatusLabel(status) returns $t.reports?.status_success
expect(screen.getByText('Success')).toBeDefined();
@@ -61,7 +68,7 @@ describe('ReportCard UX Contract', () => {
// @UX_RECOVERY: Missing fields are rendered with explicit placeholder text.
it('should render placeholders for missing fields', () => {
const partialReport = { report_id: 'partial-1' };
render(ReportCard, { report: partialReport });
render(ReportCard, { report: partialReport, onselect: vi.fn() });
// Check placeholders (using text from mocked $t)
const placeholders = screen.getAllByText('Not provided');
@@ -79,7 +86,7 @@ describe('ReportCard UX Contract', () => {
summary: "Test Summary",
updated_at: "2024-01-01"
};
render(ReportCard, { report: validReportCard });
render(ReportCard, { report: validReportCard, onselect: vi.fn() });
expect(screen.getByText('Test Summary')).toBeDefined();
expect(screen.getByText('Success')).toBeDefined();
@@ -87,14 +94,14 @@ describe('ReportCard UX Contract', () => {
// @TEST_EDGE empty_report_object
it('should handle completely empty report object gracefully', () => {
render(ReportCard, { report: {} });
render(ReportCard, { report: {}, onselect: vi.fn() });
const placeholders = screen.getAllByText('Not provided');
expect(placeholders.length).toBeGreaterThan(0);
});
// @TEST_EDGE random_status
it('should render random status directly if no translation matches', () => {
render(ReportCard, { report: { status: "unknown_status_code" } });
render(ReportCard, { report: { status: "unknown_status_code" }, onselect: vi.fn() });
expect(screen.getByText('unknown_status_code')).toBeDefined();
});
});