feat(phase-13): update frontend types and API clients for Red/Blue workflow (T-113, T-114)
T-113: Rewrite models.ts with v2 types - TestState now includes red_executing/blue_evaluating, add TeamSide, ValidationStatus, TestTemplate, TestTemplateSummary, TestTimelineEntry types, RED_EDITABLE_STATES/BLUE_EDITABLE_STATES constants, and dual validation fields on Test interface. Remove old validated_by/validated_at references from TestDetailPage and techniques API. T-114: Rewrite tests.ts API client with 16 functions covering full Red/Blue workflow (createTestFromTemplate, updateTestRed/Blue, startExecution, submitRed/Blue, validateAsRedLead/BlueLead, reopenTest, getTestTimeline). Rewrite evidence.ts with team parameter on upload/list and new deleteEvidence. Create test-templates.ts with getTemplates, getTemplateById, getTemplatesByTechnique, createTemplate, importAtomicTests.
This commit is contained in:
@@ -1,5 +1,12 @@
|
||||
import client from "./client";
|
||||
import type { Test, TestResult } from "../types/models";
|
||||
import type {
|
||||
Test,
|
||||
TestResult,
|
||||
TestState,
|
||||
TestTimelineEntry,
|
||||
} from "../types/models";
|
||||
|
||||
// ── Payloads ───────────────────────────────────────────────────────
|
||||
|
||||
export interface TestCreatePayload {
|
||||
technique_id: string;
|
||||
@@ -19,21 +26,57 @@ export interface TestUpdatePayload {
|
||||
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 TestWithEvidences extends Test {
|
||||
evidences?: Array<{
|
||||
id: string;
|
||||
test_id: string;
|
||||
file_name: string;
|
||||
sha256_hash: string;
|
||||
uploaded_by: string | null;
|
||||
uploaded_at: string;
|
||||
download_url?: string;
|
||||
}>;
|
||||
export interface TestListFilters {
|
||||
state?: TestState;
|
||||
technique_id?: string;
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
// ── CRUD ───────────────────────────────────────────────────────────
|
||||
|
||||
/** List tests with optional filters. */
|
||||
export async function getTests(filters?: TestListFilters): Promise<Test[]> {
|
||||
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?.offset !== undefined) params.append("offset", String(filters.offset));
|
||||
if (filters?.limit !== undefined) params.append("limit", String(filters.limit));
|
||||
|
||||
const { data } = await client.get<Test[]>(
|
||||
`/tests${params.toString() ? `?${params}` : ""}`,
|
||||
);
|
||||
return data;
|
||||
}
|
||||
|
||||
/** Create a new test. */
|
||||
@@ -42,25 +85,134 @@ export async function createTest(payload: TestCreatePayload): Promise<Test> {
|
||||
return data;
|
||||
}
|
||||
|
||||
/** Get test by ID with evidences. */
|
||||
export async function getTestById(testId: string): Promise<TestWithEvidences> {
|
||||
const { data } = await client.get<TestWithEvidences>(`/tests/${testId}`);
|
||||
/** Create a test from an existing template. */
|
||||
export async function createTestFromTemplate(
|
||||
templateId: string,
|
||||
techniqueId: string,
|
||||
): Promise<Test> {
|
||||
const { data } = await client.post<Test>("/tests/from-template", {
|
||||
template_id: templateId,
|
||||
technique_id: techniqueId,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
/** Get test by ID (with evidences). */
|
||||
export async function getTestById(testId: string): Promise<Test> {
|
||||
const { data } = await client.get<Test>(`/tests/${testId}`);
|
||||
return data;
|
||||
}
|
||||
|
||||
/** Update a test (only draft/rejected). */
|
||||
export async function updateTest(testId: string, payload: TestUpdatePayload): Promise<Test> {
|
||||
export async function updateTest(
|
||||
testId: string,
|
||||
payload: TestUpdatePayload,
|
||||
): Promise<Test> {
|
||||
const { data } = await client.patch<Test>(`/tests/${testId}`, payload);
|
||||
return data;
|
||||
}
|
||||
|
||||
/** Validate a test. */
|
||||
export async function validateTest(testId: string, payload: TestValidatePayload): Promise<Test> {
|
||||
const { data } = await client.post<Test>(`/tests/${testId}/validate`, payload);
|
||||
// ── Red Team ───────────────────────────────────────────────────────
|
||||
|
||||
/** Red Team updates their fields (draft, red_executing). */
|
||||
export async function updateTestRed(
|
||||
testId: string,
|
||||
payload: RedUpdatePayload,
|
||||
): Promise<Test> {
|
||||
const { data } = await client.patch<Test>(`/tests/${testId}/red`, payload);
|
||||
return data;
|
||||
}
|
||||
|
||||
/** Reject a test. */
|
||||
/** Move test from draft → red_executing. */
|
||||
export async function startExecution(testId: string): Promise<Test> {
|
||||
const { data } = await client.post<Test>(`/tests/${testId}/start-execution`);
|
||||
return data;
|
||||
}
|
||||
|
||||
/** Red Team finalises — red_executing → blue_evaluating. */
|
||||
export async function submitRedEvidence(testId: string): Promise<Test> {
|
||||
const { data } = await client.post<Test>(`/tests/${testId}/submit-red`);
|
||||
return data;
|
||||
}
|
||||
|
||||
// ── Blue Team ──────────────────────────────────────────────────────
|
||||
|
||||
/** Blue Team updates their fields (blue_evaluating only). */
|
||||
export async function updateTestBlue(
|
||||
testId: string,
|
||||
payload: BlueUpdatePayload,
|
||||
): Promise<Test> {
|
||||
const { data } = await client.patch<Test>(`/tests/${testId}/blue`, payload);
|
||||
return data;
|
||||
}
|
||||
|
||||
/** Blue Team finalises — blue_evaluating → in_review. */
|
||||
export async function submitBlueEvidence(testId: string): Promise<Test> {
|
||||
const { data } = await client.post<Test>(`/tests/${testId}/submit-blue`);
|
||||
return data;
|
||||
}
|
||||
|
||||
// ── Lead Validation ────────────────────────────────────────────────
|
||||
|
||||
/** Red Lead approves/rejects the red side. */
|
||||
export async function validateAsRedLead(
|
||||
testId: string,
|
||||
payload: RedValidationPayload,
|
||||
): Promise<Test> {
|
||||
const { data } = await client.post<Test>(
|
||||
`/tests/${testId}/validate-red`,
|
||||
payload,
|
||||
);
|
||||
return data;
|
||||
}
|
||||
|
||||
/** Blue Lead approves/rejects the blue side. */
|
||||
export async function validateAsBlueLead(
|
||||
testId: string,
|
||||
payload: BlueValidationPayload,
|
||||
): Promise<Test> {
|
||||
const { data } = await client.post<Test>(
|
||||
`/tests/${testId}/validate-blue`,
|
||||
payload,
|
||||
);
|
||||
return data;
|
||||
}
|
||||
|
||||
// ── Reopen ─────────────────────────────────────────────────────────
|
||||
|
||||
/** Reopen a rejected test — moves back to draft. */
|
||||
export async function reopenTest(testId: string): Promise<Test> {
|
||||
const { data } = await client.post<Test>(`/tests/${testId}/reopen`);
|
||||
return data;
|
||||
}
|
||||
|
||||
// ── Timeline ───────────────────────────────────────────────────────
|
||||
|
||||
/** Get the audit-log timeline for a test. */
|
||||
export async function getTestTimeline(
|
||||
testId: string,
|
||||
): Promise<TestTimelineEntry[]> {
|
||||
const { data } = await client.get<TestTimelineEntry[]>(
|
||||
`/tests/${testId}/timeline`,
|
||||
);
|
||||
return data;
|
||||
}
|
||||
|
||||
// ── Legacy (kept for backwards compat) ─────────────────────────────
|
||||
|
||||
/** Validate a test (legacy endpoint). */
|
||||
export async function validateTest(
|
||||
testId: string,
|
||||
payload: TestValidatePayload,
|
||||
): Promise<Test> {
|
||||
const { data } = await client.post<Test>(
|
||||
`/tests/${testId}/validate`,
|
||||
payload,
|
||||
);
|
||||
return data;
|
||||
}
|
||||
|
||||
/** Reject a test (legacy endpoint). */
|
||||
export async function rejectTest(testId: string): Promise<Test> {
|
||||
const { data } = await client.post<Test>(`/tests/${testId}/reject`);
|
||||
return data;
|
||||
|
||||
Reference in New Issue
Block a user