ai-coding-mines목차GitHub

성공 응답이 거짓말하는 다섯 가지 방식

쓰기 API

하루에 다섯 종류를 다 겪은 날이 있어서 표로 굳혔다.

응답실제 상황
200 + code=ERROR심사중 리소스를 수정하려 했다. HTTP는 200이다
200 + rows=0존재하지 않는 enum 값으로 필터링했다. 에러가 아니라 빈 결과다
200 + code=SUCCESS + 미반영부분 업데이트가 조용히 무시됐다. 26분 뒤에도 값이 그대로다
200 + code=SUCCESS + 경고경고가 붙었지만 실제로는 반영됐다. 경고를 실패로 읽으면 정상 건을 재시도한다
400 PRECONDITION_FAILED그냥 존재하지 않는 경로다. 조건 위반이 아니다

성공과 실패의 축이 최소 세 개다 — HTTP 상태, 응답 본문의 code, 그리고 재조회했을 때의 실제 값. 앞의 둘이 다 통과해도 세 번째가 다를 수 있다.

강제 장치

def ok(code, resp):
    return code == 200 and isinstance(resp, dict) and resp.get("code") != "ERROR"

이걸 안 넣어 700여 건을 헛돌렸다. 전부 거부됐는데 진행 파일에는 성공으로 기록됐다.

그리고 이걸로도 부족하다. 위 표의 3번은 ok()를 통과한다. 쓰기 후에는 반드시 단건 재조회로 실제 값을 확인한다. 변경 이력 엔드포인트가 있으면 그게 더 확실하다.

역방향 — 실패로 보이는 성공

쓰기 성공 응답의 message가 문자열 "[]"로 왔다. 빈 배열이 아니라 빈 배열처럼 생긴 truthy 문자열이다. if msg:로 오류를 판정하던 코드가 정상 반영된 건들을 실패로 기록했다. 빈 값의 표현은 타입마다 다르다 — "" · [] · "[]" · {} · null. 존재 여부가 아니라 내용으로 판정한다.

오염된 실패 목록은 그 위에 쌓은 진단을 전부 무효로 만든다. 그 "실패" 건들을 근거로 "기존 값이 원인"이라는 틀린 진단을 세웠고, 진범은 따로 있었다. 판정 함수 하나가 틀리면 배치 결과뿐 아니라 그 결과로 내린 결론까지 버려야 한다.

여섯 번째 — 거부 목록을 안 읽었다

추적 등록 API가 acceptedrejected따로 돌려주는데, 래퍼가 거부 쪽을 무시하고 전건을 로컬 DB에 "등록됨"으로 기록했다. 운송사 자동 감지 실패로 거부된 건이 조회 불가 상태로 방치됐다. accepted 개수를 세고, 0이면 경고한다.

그리고 accepted도 끝이 아니다. 등록은 됐는데 추적은 "없음"인 경우가 있다 — "접수됨"은 등록 성공이지 추적 성공이 아니다. 성공에 한 겹이 더 있다.