language를 바꾼 SQL에 다른 컬럼의 변경이 섞였다
AI Commons의 클론 보이스 생성 코드는 availableModels를 삽입 데이터에 넣지 않는다. 필수 배열이지만 DB의 빈 배열 기본값이 채워 준다는 계약이다. 공용 보이스 동기화처럼 필드를 직접 넣는 경로와는 다른 의존성이 있다.
language를 nullable로 바꾸는 마이그레이션은 그 계약도 바꿨다. 실행 SQL에는 availableModels의 기본값 제거가 함께 들어 있었다.
ALTER TABLE "public"."Voice"
ALTER COLUMN "language" DROP NOT NULL,
ALTER COLUMN "availableModels" DROP DEFAULT; 생략한 컬럼에 넣을 기본값이 사라지면 생성 경로가 기대한 필수 배열을 채울 수 없다. 서비스 함수의 입력 타입이나 이름이 그대로여도 DB에 생략한 값이 어떻게 저장되는지는 달라진다. 변경 대상의 이름만 보고 마이그레이션을 검토하면 놓치기 쉬운 부분이다.
한 호출에 빈 배열을 넣는 대신 기본값 계약을 돌려놓았다
복구는 Prisma 스키마에 String[] @default([])를 명시하고, 새 SQL 마이그레이션에서 실제 컬럼 기본값을 다시 설정했다.
ALTER TABLE "public"."Voice"
ALTER COLUMN "availableModels" SET DEFAULT '{}';
UPDATE "public"."Voice"
SET "availableModels" = '{}'
WHERE "availableModels" IS NULL; 클론 생성 한 곳에 availableModels: []를 추가하면 그 호출은 기본값에 덜 의존하게 된다. 하지만 컬럼을 생략하는 다른 경로와 스키마의 불일치는 남는다. 기본값 복구는 생략한 삽입의 의미를 다시 공통으로 맞춘다. 스키마 파일만 바꿔서는 배포된 DB가 달라지지 않으므로 SQL 적용이 필요하다.
null 보정 UPDATE는 안전 조치다. 이 문장이 있다는 사실만으로 실제 null 행이 존재했다고 판단할 수는 없다. NOT NULL 제약 때문에 삽입이 실패했다면 행이 생기지 않는 것이 보통이다. 또한 이 복구는 배열 기본값을 되돌렸으며, SQL에 함께 있던 인덱스 삭제까지 모두 되돌린 변경은 아니다.
필드 생략을 확인한 테스트는 DB 기본값을 확인하지 않는다
추가된 서비스 테스트는 모킹한 voice.create 호출의 data에 availableModels가 없는지 검사한다. 생성 코드가 계속 기본값에 위임한다는 의도를 표현하지만, DB를 쓰지 않으므로 기본값이 다시 제거돼도 이 검사 자체는 통과할 수 있다.
서비스 mock은 SQL 기본값을 검증하지 않는다. 계약을 DB까지 확인하려면 실제 마이그레이션을 적용한 스키마에서 필드를 생략해 삽입하고 빈 배열이 저장되는지 보아야 한다. 이 사례에서 검토할 대상은 TypeScript의 생성 데이터뿐 아니라, 그 데이터가 생략한 값을 완성하는 SQL 기본값이다.