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:
2026-02-09 10:57:48 +01:00
parent 9d7832c571
commit d660bceeb4
6 changed files with 447 additions and 68 deletions
+60 -14
View File
@@ -1,30 +1,76 @@
import client from "./client";
import type { Evidence, TeamSide } from "../types/models";
export interface EvidenceOut {
id: string;
test_id: string;
file_name: string;
sha256_hash: string;
uploaded_by: string | null;
uploaded_at: string;
// ── Response type (with download URL) ──────────────────────────────
export interface EvidenceOut extends Evidence {
download_url: string;
}
/** Upload evidence file for a test. */
export async function uploadEvidence(testId: string, file: File): Promise<EvidenceOut> {
// ── Upload ─────────────────────────────────────────────────────────
/** Upload an evidence file for the given test.
*
* The ``team`` field is sent as form data alongside the file so the
* backend can enforce Red/Blue access control.
*/
export async function uploadEvidence(
testId: string,
file: File,
team: TeamSide,
notes?: string,
): Promise<EvidenceOut> {
const formData = new FormData();
formData.append("file", file);
formData.append("team", team);
if (notes) {
formData.append("notes", notes);
}
const { data } = await client.post<EvidenceOut>(`/tests/${testId}/evidence`, formData, {
headers: {
"Content-Type": "multipart/form-data",
const { data } = await client.post<EvidenceOut>(
`/tests/${testId}/evidence`,
formData,
{
headers: {
"Content-Type": "multipart/form-data",
},
},
});
);
return data;
}
/** Get evidence metadata with download URL. */
// ── List ───────────────────────────────────────────────────────────
/** List evidences for a test, optionally filtered by team. */
export async function getTestEvidences(
testId: string,
team?: TeamSide,
): Promise<EvidenceOut[]> {
const params = new URLSearchParams();
if (team) params.append("team", team);
const { data } = await client.get<EvidenceOut[]>(
`/tests/${testId}/evidence${params.toString() ? `?${params}` : ""}`,
);
return data;
}
// ── Detail ─────────────────────────────────────────────────────────
/** Get evidence metadata with presigned download URL. */
export async function getEvidence(evidenceId: string): Promise<EvidenceOut> {
const { data } = await client.get<EvidenceOut>(`/evidence/${evidenceId}`);
return data;
}
// ── Delete ─────────────────────────────────────────────────────────
/** Delete an evidence record (only in editable states). */
export async function deleteEvidence(
evidenceId: string,
): Promise<{ detail: string }> {
const { data } = await client.delete<{ detail: string }>(
`/evidence/${evidenceId}`,
);
return data;
}