feat(studio-save): 시험 초안 계획에 두 번째 저장과 정리 단계를 넣는다

만들기만 하면 이 런에서 expectedVersion·VERSION_CONFLICT 경로가 한 번도 안 돈다. 새로
만든 뒤 한 번 더 저장해 낙관적 락을 태운다. expectedVersion 은 만들기 응답이 준 version
이고, 모른 채 보내면 서버가 0 으로 채워 늘 충돌한다.

만든 시험 초안을 그 자리에서 지운다. DELETE /api/v1/studio/cases/{id} 가 실재하는 것을
소스와 openapi 에서 확인했고 게시 가드에 안 걸리는 것도 확인했다. 시험 초안의 종류를
CASE 로 고른 이유가 그것이다 — Decision 은 계약에 삭제 경로가 없다.

정리 단계는 --harness-test 일 때만 낸다. 진짜 기록에는 삭제 요청을 만들지 않는다.

삭제가 409 를 낼 수 있다 — 공개된 기록이거나 참조하는 곳이 있는 경우다. 둘 다 사람이
화면에서 처리하고, 계획에 그렇게 적었다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4vKjQo9KKBBokzxqXLCfk
This commit is contained in:
DongHyeonka
2026-09-10 19:15:07 +09:00
co-authored by Claude Opus 5
parent f16785b93e
commit c66eee6cb2
2 changed files with 81 additions and 2 deletions
+41
View File
@@ -489,3 +489,44 @@ class PublishedRecordGuardTest(unittest.TestCase):
def test_the_harness_prefix_is_defined(self):
"""시험 초안을 사람이 눈으로 가릴 수 있어야 한다."""
self.assertEqual("[HARNESS-TEST]", ss.HARNESS_PREFIX)
class HarnessTestPlanTest(unittest.TestCase):
"""시험 초안 계획. 만든 것을 그 자리에서 지운다."""
def test_cleanup_only_appears_for_a_harness_test(self):
"""진짜 기록에는 삭제 요청을 만들지 않는다."""
plain = ss.plan_requests("docs/p/t/case/x.md", dict(SENT), None, None)
self.assertNotIn("cleanup", [s["op"] for s in plain])
harness = ss.plan_requests("docs/p/t/case/x.md", dict(SENT), None, None,
harness_test=True)
self.assertIn("cleanup", [s["op"] for s in harness])
def test_the_cleanup_path_is_the_case_delete_endpoint(self):
"""`DELETE /api/v1/studio/cases/{id}` 는 실재한다
(`ManagementDocumentController.java:72`). Decision 은 삭제 경로가 없어서
시험 초안의 종류를 CASE 로 고른다."""
steps = ss.plan_requests("docs/p/t/case/x.md", dict(SENT), None, None,
harness_test=True)
cleanup = next(s for s in steps if s["op"] == "cleanup")
self.assertEqual("DELETE", cleanup["method"])
self.assertIn("/api/v1/studio/cases/", cleanup["path"])
def test_the_cleanup_path_does_not_trip_the_publish_guard(self):
"""삭제 경로가 게시 가드에 걸리면 정리를 못 한다."""
ss._reject_forbidden_paths([{"path": "/api/v1/studio/cases/abc"}])
def test_a_second_save_exercises_the_optimistic_lock(self):
"""이 단계가 없으면 이 런에서 `expectedVersion` 경로가 안 돈다."""
steps = ss.plan_requests("docs/p/t/case/x.md", dict(SENT), None, None,
harness_test=True)
save = next(s for s in steps if s["op"] == "save-after-create")
self.assertIn("expectedVersion", save["body"])
self.assertIn("VERSION_CONFLICT", save["expect"]["onConflict"])
def test_a_harness_plan_still_has_no_publish_path(self):
steps = ss.plan_requests("docs/p/t/case/x.md", dict(SENT), None, None,
harness_test=True)
for s in steps:
self.assertNotIn("/publish", s["path"])
self.assertNotIn("/unpublish", s["path"])