fix: scrub training public import response

This commit is contained in:
2026-04-30 22:26:59 -04:00
parent 175b2f0dc8
commit d15d9b9152
2 changed files with 123 additions and 1 deletions
@@ -96,7 +96,7 @@ class TrainingCaseService:
return _sanitize_public_export(export_training_cases(cases))
def import_cases(self, payload: dict) -> dict:
return import_training_cases(payload, repository=self.repository)
return _sanitize_public_export(import_training_cases(payload, repository=self.repository))
def _sanitize_public_export(value):
@@ -260,6 +260,75 @@ def _public_import_package() -> dict:
}
def _nested_public_import_package() -> dict:
return {
"version": 1,
"items": [
{
"external_id": "m19-import-ref " + INTERNAL_FIELD,
"title": "Imported response privacy",
"scenario": {
"instrument_id": 1,
"timeframe": "15m",
"case_type": "sweep",
"start_ts": "2026-04-22T09:00:00Z",
"decision_ts": "2026-04-22T10:00:00Z",
"end_ts": "2026-04-22T11:00:00Z",
"source_event_refs": [
{
"kind": "sweep_event",
"id": 19,
"answer": NESTED_PRIVATE_MARKERS[0],
INTERNAL_FIELD: NESTED_PRIVATE_MARKERS[1],
},
{"kind": "context", "id": 20, "label": PRIVATE_FIELD_TEXT},
],
},
"skill_tags": ["sweep", "context_reading"],
"difficulty": "medium",
"status": "draft",
"answer": {
"side": NESTED_PRIVATE_MARKERS[0],
"nested": {
"close": NESTED_PRIVATE_MARKERS[1],
INTERNAL_FIELD: NESTED_PRIVATE_MARKERS[2],
},
},
"rubric": [
{
"category": NESTED_PRIVATE_MARKERS[2],
"max_score": 5,
"answer": NESTED_PRIVATE_MARKERS[0],
INTERNAL_FIELD: {"detail": NESTED_PRIVATE_MARKERS[1]},
}
],
"tasks": [
{
"task_type": "short_answer",
"skill_tag": "sweep",
"prompt": "Describe the public confirmation.",
"required_annotation_types": ["text"],
"max_score": 5,
"meta": {
"public_hint": "Focus on context.",
"answer": NESTED_PRIVATE_MARKERS[0],
INTERNAL_FIELD: NESTED_PRIVATE_MARKERS[1],
"nested": {"answer": NESTED_PRIVATE_MARKERS[2]},
},
}
],
"metadata": {
"chapter_ref": "chapter_19",
"public_label": "visible",
"answer": NESTED_PRIVATE_MARKERS[0],
INTERNAL_FIELD: NESTED_PRIVATE_MARKERS[1],
"nested": {"answer": NESTED_PRIVATE_MARKERS[2]},
},
}
],
}
def _dict_keys(value) -> list[str]:
if isinstance(value, dict):
keys = []
@@ -411,6 +480,59 @@ class TrainingPublicApiContractM8Tests(unittest.TestCase):
with self.subTest(private_text=private_text):
self.assertNotIn(private_text, serialized)
def test_public_training_import_route_scrubs_nested_solution_material_from_response(self) -> None:
repository = _PublicTrainingRepository()
service = TrainingCaseService(repository=repository)
dependencies = _training_dependencies(service)
status, payload = server.route_request(
"/training/cases/import",
{},
dependencies=dependencies,
method="POST",
body=_nested_public_import_package(),
)
self.assertEqual(status, 201)
self.assertEqual(payload["imported"], 1)
self.assertEqual(payload["case_ids"], [41])
item = payload["items"][0]
self.assertEqual(
set(item),
{"id", "title", "scenario", "skill_tags", "difficulty", "status", "external_id"},
)
self.assertEqual(item["id"], 41)
self.assertEqual(item["title"], "Imported response privacy")
self.assertEqual(item["external_id"], "m19-import-ref private solution field")
self.assertEqual(item["skill_tags"], ["sweep", "context_reading"])
self.assertEqual(item["difficulty"], "medium")
self.assertEqual(item["status"], "draft")
self.assertEqual(
item["scenario"],
{
"instrument_id": 1,
"timeframe": "15m",
"case_type": "sweep",
"start_ts": "2026-04-22T09:00:00Z",
"decision_ts": "2026-04-22T10:00:00Z",
"end_ts": "2026-04-22T11:00:00Z",
"source_event_refs": [
{"id": 19, "kind": "sweep_event"},
{"id": 20, "kind": "context", "label": "public text mentions private solution field"},
],
},
)
serialized = json.dumps(payload, sort_keys=True)
self.assertNotIn(INTERNAL_FIELD, serialized)
for private_text in NESTED_PRIVATE_MARKERS:
with self.subTest(private_text=private_text):
self.assertNotIn(private_text, serialized)
keys = _dict_keys(payload)
self.assertNotIn("answer", keys)
self.assertNotIn(INTERNAL_FIELD, keys)
def test_public_training_list_detail_routes_scrub_nested_solution_material(self) -> None:
repository = _NestedPublicTrainingRepository()
service = TrainingCaseService(repository=repository)