import client from "./client"; import type { Test, TestResult, TestState, TestTimelineEntry, } from "../types/models"; // ── Payloads ─────────────────────────────────────────────────────── export interface TestCreatePayload { technique_id: string; name: string; description?: string; platform?: string; procedure_text?: string; tool_used?: string; } export interface TestUpdatePayload { name?: string; description?: string; platform?: string; procedure_text?: string; tool_used?: string; result?: TestResult; } export interface RedUpdatePayload { name?: string; description?: string; procedure_text?: string; tool_used?: string; attack_success?: boolean; red_summary?: string; } export interface BlueUpdatePayload { detection_result?: TestResult; blue_summary?: string; } export interface RedValidationPayload { red_validation_status: "approved" | "rejected"; red_validation_notes?: string; } export interface BlueValidationPayload { blue_validation_status: "approved" | "rejected"; blue_validation_notes?: string; } /** Legacy payload — kept for backwards compat. */ export interface TestValidatePayload { result: TestResult; comments?: string; } export interface TestListFilters { state?: TestState; technique_id?: string; platform?: string; created_by?: string; pending_validation_side?: "red" | "blue"; not_in_any_campaign?: boolean; offset?: number; limit?: number; } // ── CRUD ─────────────────────────────────────────────────────────── /** List tests with optional filters. */ export async function getTests(filters?: TestListFilters): Promise { const params = new URLSearchParams(); if (filters?.state) params.append("state", filters.state); if (filters?.technique_id) params.append("technique_id", filters.technique_id); if (filters?.platform) params.append("platform", filters.platform); if (filters?.created_by) params.append("created_by", filters.created_by); if (filters?.pending_validation_side) params.append("pending_validation_side", filters.pending_validation_side); if (filters?.not_in_any_campaign) params.append("not_in_any_campaign", "true"); if (filters?.offset !== undefined) params.append("offset", String(filters.offset)); if (filters?.limit !== undefined) params.append("limit", String(filters.limit)); const { data } = await client.get( `/tests${params.toString() ? `?${params}` : ""}`, ); return data; } /** Create a new test. */ export async function createTest(payload: TestCreatePayload): Promise { const { data } = await client.post("/tests", payload); return data; } /** Create a test from an existing template, with optional field overrides. */ export async function createTestFromTemplate( templateId: string, techniqueId: string, overrides?: { name?: string; description?: string; platform?: string; procedure_text?: string; tool_used?: string; }, ): Promise { const { data } = await client.post("/tests/from-template", { template_id: templateId, technique_id: techniqueId, ...overrides, }); return data; } /** Get test by ID (with evidences). */ export async function getTestById(testId: string): Promise { const { data } = await client.get(`/tests/${testId}`); return data; } /** Update a test (only draft/rejected). */ export async function updateTest( testId: string, payload: TestUpdatePayload, ): Promise { const { data } = await client.patch(`/tests/${testId}`, payload); return data; } // ── Red Team ─────────────────────────────────────────────────────── /** Red Team updates their fields (draft, red_executing). */ export async function updateTestRed( testId: string, payload: RedUpdatePayload, ): Promise { const { data } = await client.patch(`/tests/${testId}/red`, payload); return data; } /** Move test from draft → red_executing. */ export async function startExecution(testId: string): Promise { const { data } = await client.post(`/tests/${testId}/start-execution`); return data; } /** Red Team finalises — red_executing → blue_evaluating. */ export async function submitRedEvidence(testId: string): Promise { const { data } = await client.post(`/tests/${testId}/submit-red`); return data; } // ── Timer Controls ───────────────────────────────────────────────── /** Pause the active phase timer. */ export async function pauseTimer(testId: string): Promise { const { data } = await client.post(`/tests/${testId}/pause-timer`); return data; } /** Resume a paused phase timer. */ export async function resumeTimer(testId: string): Promise { const { data } = await client.post(`/tests/${testId}/resume-timer`); return data; } // ── Blue Team ────────────────────────────────────────────────────── /** Blue Team updates their fields (blue_evaluating only). */ export async function updateTestBlue( testId: string, payload: BlueUpdatePayload, ): Promise { const { data } = await client.patch(`/tests/${testId}/blue`, payload); return data; } /** Blue Team finalises — blue_evaluating → in_review. */ export async function submitBlueEvidence(testId: string): Promise { const { data } = await client.post(`/tests/${testId}/submit-blue`); return data; } /** Blue tech picks up the test to start evaluating — sets the Tempo timer start. */ export async function startBlueWork(testId: string): Promise { const { data } = await client.post(`/tests/${testId}/start-blue-work`); return data; } // ── Lead Validation ──────────────────────────────────────────────── /** Red Lead approves/rejects the red side. */ export async function validateAsRedLead( testId: string, payload: RedValidationPayload, ): Promise { const { data } = await client.post( `/tests/${testId}/validate-red`, payload, ); return data; } /** Blue Lead approves/rejects the blue side. */ export async function validateAsBlueLead( testId: string, payload: BlueValidationPayload, ): Promise { const { data } = await client.post( `/tests/${testId}/validate-blue`, payload, ); return data; } // ── Reopen ───────────────────────────────────────────────────────── /** Reopen a rejected test — moves back to draft. */ export async function reopenTest(testId: string): Promise { const { data } = await client.post(`/tests/${testId}/reopen`); return data; } // ── Timeline ─────────────────────────────────────────────────────── /** Get the audit-log timeline for a test. */ export async function getTestTimeline( testId: string, ): Promise { const { data } = await client.get( `/tests/${testId}/timeline`, ); return data; } // ── Retest Chain ──────────────────────────────────────────────────── export interface RetestChainEntry { id: string; name: string; state: string | null; retest_of: string | null; retest_count: number; result: string | null; detection_result: string | null; remediation_status: string | null; created_at: string | null; } /** Get the full retest chain for a test. */ export async function getRetestChain(testId: string): Promise { const { data } = await client.get(`/tests/${testId}/retest-chain`); return data; } // ── Legacy (kept for backwards compat) ───────────────────────────── /** Validate a test (legacy endpoint). */ export async function validateTest( testId: string, payload: TestValidatePayload, ): Promise { const { data } = await client.post( `/tests/${testId}/validate`, payload, ); return data; } /** Reject a test (legacy endpoint). */ export async function rejectTest(testId: string): Promise { const { data } = await client.post(`/tests/${testId}/reject`); return data; } // ── Tempo sync ───────────────────────────────────────────────────── export interface TempoSyncResult { worklog_id: string; status: "synced" | "already_synced" | "skipped" | "error"; detail?: string; } // ── RT Import ────────────────────────────────────────────────────── export interface RTTechniqueEntry { mitre_id: string; result: "detected" | "not_detected" | "partially_detected"; attack_success: boolean; platform?: string; notes?: string; } export interface RTImportPayload { name: string; date?: string; description?: string; operator?: string; techniques: RTTechniqueEntry[]; } export interface RTImportResult { created: number; skipped: number; items: { mitre_id: string; test_name: string; result: string; attack_success: boolean }[]; warnings: { mitre_id: string; reason: string }[]; engagement: string; } /** Import results from a real Red Team engagement. */ export async function importRT(payload: RTImportPayload): Promise { const { data } = await client.post("/tests/import-rt", payload); return data; } /** Manually push this test's red team execution worklog to Tempo. */ export async function syncTestToTempo( testId: string, ): Promise<{ results: TempoSyncResult[] }> { const { data } = await client.post<{ results: TempoSyncResult[] }>( `/tests/${testId}/sync-tempo`, ); return data; }