fix(tests): apply user edits when creating test from template

The form captured name/description/platform/procedure/tool edits but
never sent them — the created test always used the raw template values.

- TestTemplateInstantiate schema: add optional override fields
  (name, description, platform, procedure_text, tool_used)
- create_test_from_template service: accept *_override kwargs;
  use override value when provided, fall back to template value
- Router: pass all override fields from payload to service
- Frontend API createTestFromTemplate: accept overrides object, spread into body
- TestFromTemplateForm: pass all form state values as overrides
This commit is contained in:
kitos
2026-05-28 16:38:40 +02:00
parent 785b5b44a3
commit 965ff96433
5 changed files with 45 additions and 8 deletions
+5
View File
@@ -185,6 +185,11 @@ def create_test_from_template(
template_id=payload.template_id, template_id=payload.template_id,
technique_id_or_mitre=payload.technique_id, technique_id_or_mitre=payload.technique_id,
creator_id=current_user.id, creator_id=current_user.id,
name_override=payload.name,
description_override=payload.description,
platform_override=payload.platform,
procedure_text_override=payload.procedure_text,
tool_used_override=payload.tool_used,
) )
log_action( log_action(
db, db,
+11 -1
View File
@@ -72,7 +72,17 @@ class TestTemplateSummary(BaseModel):
class TestTemplateInstantiate(BaseModel): class TestTemplateInstantiate(BaseModel):
"""Payload to create a real test from an existing template.""" """Payload to create a real test from an existing template.
Optional override fields take precedence over the template values when provided.
"""
template_id: uuid.UUID template_id: uuid.UUID
technique_id: str # accepts both UUID and MITRE ID (e.g. "T1059.001") technique_id: str # accepts both UUID and MITRE ID (e.g. "T1059.001")
# User-editable overrides (if omitted the template value is used)
name: str | None = None
description: str | None = None
platform: str | None = None
procedure_text: str | None = None
tool_used: str | None = None
+12 -5
View File
@@ -93,10 +93,17 @@ def create_test_from_template(
template_id: uuid.UUID, template_id: uuid.UUID,
technique_id_or_mitre: str, technique_id_or_mitre: str,
creator_id: uuid.UUID, creator_id: uuid.UUID,
# Optional user-edited overrides (take priority over template values)
name_override: str | None = None,
description_override: str | None = None,
platform_override: str | None = None,
procedure_text_override: str | None = None,
tool_used_override: str | None = None,
) -> Test: ) -> Test:
"""Instantiate a Test from a TestTemplate. """Instantiate a Test from a TestTemplate.
technique_id_or_mitre can be a UUID string or MITRE ID (e.g. T1059.001). technique_id_or_mitre can be a UUID string or MITRE ID (e.g. T1059.001).
Override fields, when provided, take precedence over the template's values.
Raises EntityNotFoundError if template or technique not found. Raises EntityNotFoundError if template or technique not found.
Does not commit; caller uses UnitOfWork. Does not commit; caller uses UnitOfWork.
""" """
@@ -121,11 +128,11 @@ def create_test_from_template(
test = Test( test = Test(
technique_id=technique.id, technique_id=technique.id,
name=template.name, name=name_override if name_override is not None else template.name,
description=template.description, description=description_override if description_override is not None else template.description,
platform=template.platform, platform=platform_override if platform_override is not None else template.platform,
procedure_text=template.attack_procedure, procedure_text=procedure_text_override if procedure_text_override is not None else template.attack_procedure,
tool_used=template.tool_suggested, tool_used=tool_used_override if tool_used_override is not None else template.tool_suggested,
remediation_steps=template.suggested_remediation, remediation_steps=template.suggested_remediation,
created_by=creator_id, created_by=creator_id,
state=TestState.draft, state=TestState.draft,
+9 -1
View File
@@ -91,14 +91,22 @@ export async function createTest(payload: TestCreatePayload): Promise<Test> {
return data; return data;
} }
/** Create a test from an existing template. */ /** Create a test from an existing template, with optional field overrides. */
export async function createTestFromTemplate( export async function createTestFromTemplate(
templateId: string, templateId: string,
techniqueId: string, techniqueId: string,
overrides?: {
name?: string;
description?: string;
platform?: string;
procedure_text?: string;
tool_used?: string;
},
): Promise<Test> { ): Promise<Test> {
const { data } = await client.post<Test>("/tests/from-template", { const { data } = await client.post<Test>("/tests/from-template", {
template_id: templateId, template_id: templateId,
technique_id: techniqueId, technique_id: techniqueId,
...overrides,
}); });
return data; return data;
} }
@@ -68,7 +68,14 @@ export default function TestFromTemplateForm({
// ── Submit ───────────────────────────────────────────────────── // ── Submit ─────────────────────────────────────────────────────
const createMutation = useMutation({ const createMutation = useMutation({
mutationFn: () => createTestFromTemplate(templateId, technique), mutationFn: () =>
createTestFromTemplate(templateId, technique, {
name: name.trim() || undefined,
description: description.trim() || undefined,
platform: platform.trim() || undefined,
procedure_text: procedureText.trim() || undefined,
tool_used: toolUsed.trim() || undefined,
}),
onSuccess: (test) => { onSuccess: (test) => {
navigate(`/tests/${test.id}`); navigate(`/tests/${test.id}`);
}, },