fix(studio-save): 정리 단계가 본문 없이 나가고 종류를 안 봤다
DELETE 가 본문을 요구한다. requestBody: required: true 이고 컨트롤러가 @RequestBody ExpectedVersionRequest 를 받는다. 본문 없이 보내면 400 이고 초안이 남는다. expectedVersion 은 verify 가 읽은 값이어야 한다 — 만든 뒤 한 번 더 저장하므로 만들 때 version 이 아니다. 경로가 종류를 안 보고 무조건 cases 로 나갔다. 서버는 종류를 조회 조건에 넣고 그 이유를 코드에 적어 두었다 — 그렇게 하지 않으면 Case 경로로 Reference 를 지울 수 있게 되기 때문이다. 경로가 종류와 어긋나면 DOCUMENT_NOT_FOUND 가 나고 초안이 남는다. 시험 초안을 CASE 로 쓴 판단은 맞았지만 코드가 그 판단에 기대고 있었다. 종류별 경로를 표로 만들고, Decision 처럼 프로젝트 id 가 필요한 것과 모르는 종류는 거절한다 — 조용히 받으면 못 지우는 초안이 운영에 남는다. CSRF 헤더 이름과 404 의 뜻도 계획에 적었다. X-XSRF-TOKEN 이면 403 이다. 이 고침은 계약에 맞춘 것이지 걸어 본 것이 아니다. 계획대로 보내 본 사람은 아직 없다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q4vKjQo9KKBBokzxqXLCfk
This commit is contained in:
co-authored by
Claude Opus 5
parent
e1404f8a3d
commit
96d89ec6da
@@ -502,6 +502,55 @@ class HarnessTestPlanTest(unittest.TestCase):
|
||||
harness_test=True)
|
||||
self.assertIn("cleanup", [s["op"] for s in harness])
|
||||
|
||||
def test_the_cleanup_path_follows_the_kind(self):
|
||||
"""서버가 종류를 조회 조건에 넣는다.
|
||||
|
||||
「그렇게 하지 않으면 Case 경로로 Reference 를 지울 수 있게 되기 때문이다」
|
||||
(`DeleteDocumentDraftUseCase`). 경로를 종류와 안 맞추면 `DOCUMENT_NOT_FOUND` 가
|
||||
나고 **초안이 남는다.** 시험 초안을 CASE 로만 쓰면 이 결함이 안 드러난다.
|
||||
"""
|
||||
for kind, want in (("CASE", "/cases/"), ("CONCEPT", "/concepts/"),
|
||||
("REFERENCE", "/references/"), ("QUESTION", "/questions/"),
|
||||
("SETUP", "/setups/")):
|
||||
with self.subTest(kind=kind):
|
||||
steps = ss.plan_requests("docs/p/t/case/x.md", {**SENT, "kind": kind},
|
||||
None, None, harness_test=True)
|
||||
cleanup = next(s for s in steps if s["op"] == "cleanup")
|
||||
self.assertIn(want, cleanup["path"])
|
||||
|
||||
def test_the_cleanup_body_is_not_empty(self):
|
||||
"""`requestBody: required: true` 다. 안 보내면 400 이고 초안이 남는다.
|
||||
|
||||
값은 **`verify` 가 읽은 version** 이다 — 만든 뒤 한 번 더 저장하므로 만들 때
|
||||
version 이 아니다.
|
||||
"""
|
||||
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.assertIsNotNone(cleanup["body"])
|
||||
self.assertIn("expectedVersion", cleanup["body"])
|
||||
self.assertIn("verify", str(cleanup["body"]["expectedVersion"]))
|
||||
|
||||
def test_a_kind_we_cannot_delete_is_refused(self):
|
||||
"""지울 수 없는 것을 만들지 않는다.
|
||||
|
||||
Decision 은 프로젝트 아래에 있어 프로젝트 id 가 있어야 지운다. 이 어댑터는
|
||||
그 값을 모른다 — 조용히 받으면 못 지우는 초안이 운영에 남는다.
|
||||
"""
|
||||
for kind in ("PROJECT_DECISION", "MYSTERY"):
|
||||
with self.subTest(kind=kind):
|
||||
with self.assertRaises(ss.Refused):
|
||||
ss.plan_requests("docs/p/t/case/x.md", {**SENT, "kind": kind},
|
||||
None, None, harness_test=True)
|
||||
|
||||
def test_the_cleanup_names_the_csrf_header_and_the_not_found_shape(self):
|
||||
"""`X-XSRF-TOKEN` 이면 403 이 난다. 그리고 404 는 「종류가 어긋났다」의 모양이다."""
|
||||
steps = ss.plan_requests("docs/p/t/case/x.md", dict(SENT), None, None,
|
||||
harness_test=True)
|
||||
e = next(s for s in steps if s["op"] == "cleanup")["expect"]
|
||||
self.assertIn("X-CSRF-TOKEN", e["csrfHeader"])
|
||||
self.assertIn("DOCUMENT_NOT_FOUND", e["onNotFound"])
|
||||
|
||||
def test_the_cleanup_path_is_the_case_delete_endpoint(self):
|
||||
"""`DELETE /api/v1/studio/cases/{id}` 는 실재한다
|
||||
(`ManagementDocumentController.java:72`).
|
||||
|
||||
Reference in New Issue
Block a user