Opus変換マイルストーンのユニットテスト抜け穴埋め(設計)
何のためのものか
Opus変換・永続ストレージアップロード(マイルストーン2)は、TDD(implementer/testサブエージェント分離)の手順を踏まずに直接実装された。このページは、その後追いでユニットテストの抜けを洗い出した/digセッションの成果物。
前提: 既存の設計境界
internal/*パッケージは一貫して次の境界でテストを分けている。
service.goのドメインロジック — DBアクセスは狭いinterface(repo)越し。fakeで差し替え可能なのでユニットテスト対象。handler.go・tus_handler.go・worker.goのトランスポート/グルー層 — Echo/HTTPハンドラ、pgxpool.Pool直結のトランザクション管理、外部プロセス(ffmpeg)・外部サービス(S3)呼び出し。scripts/e2e-smoke.shで検証し、ユニットテスト対象外。
internal/transcode/audio.goのtranscodeAudio(ffmpeg実行・S3ダウンロード/アップロード)はこの境界の典型例で、コード内コメントに設計判断が明記されている。今回の抜け穴探しもこの境界を前提に、境界の内側(service.goのロジック)に絞って行った。境界そのものを変える(例: ffmpeg実行をinterfaceで切り出してfake化する)かどうかは別の意思決定として見送った。
ユニットテストを書く基準
「分岐の複雑さ」×「fakeで表現できる依存かどうか」の2軸で判断する。
- 両方高い(分岐が多く、依存をfake化できる) → ユニットテスト向き。
- どちらかが低い(分岐がほぼないグルーコード、または外部システムの実際の挙動が仕様の本体) → E2E任せが妥当。過去の複合IDバグ(known-findings)のように、fakeベースのテストではそもそも検出できない種類のバグもある。
洗い出した抜けと追加したテストケース
internal/transcode/service_test.go(finishUpload)
既存6ケースに加えて追加:
GetWorkがErrNoRows以外のエラーを返したときエラーが伝播する(既存はErrNoRowsのケースしかなかった)PublishPendingWorkがエラーを返したときエラーが伝播する(既存は成功ケースのみ)- workが
published(既に公開済み、再アップロード等での再変換)のときPublishPendingWorkが呼ばれない - workが
deletedのときPublishPendingWorkが呼ばれない(削除済みworkを誤って復活させないことの回帰テスト)
work_statusはdraft/publish_pending/published/deletedの4値。既存テストはdraftとpublish_pendingのみカバーしていた。
internal/works/service_test.go(publishOrPend、新規)
publishOrPend(POST /api/works/:id/publishの状態機械: タイトル必須チェック→アップロード完了済みなら即時公開、そうでなければpublish_pending)は追加前は一切テストがなかった。fakeWorksRepoのPublishWork/SetWorkPublishPending/GetWorkWithUploadStatusはreplaceTagsのテストでは使わないためpanicスタブのままだったが、これらに実装を足す。
- タイトル空文字 →
errTitleRequired、PublishWorkもSetWorkPublishPendingも呼ばれない - タイトルあり・upload未存在(
UploadStatus.Valid=false) →SetWorkPublishPendingが呼ばれる - タイトルあり・upload存在するが
done以外(transcoding/failed) →SetWorkPublishPendingが呼ばれる - タイトルあり・upload
done→PublishWorkが呼ばれる(即時公開) GetWorkWithUploadStatusがエラーを返す → エラーが伝播し後続は呼ばれない
6と7はコード上同じ分岐(row.UploadStatus.Valid && ... == Doneがfalse)に落ちるが、sqlcが生成するNullUploadStatusのValid/Invalidの意味を明示的に固定する回帰テストとしてあえて分けている。
internal/uploads/tus_handler_test.go(mapUploadError、新規)
- 各
Err*センチネルエラー(ErrWorkIDRequired・ErrWorkIDInvalid・ErrWorkNotFound・ErrWorkNotUploadable・ErrWorkAlreadyUploaded)が対応するtusd.Error(コード・HTTPステータス)にマッピングされ、未知のエラーはERR_INTERNAL/500になる
parseFfprobeDurationと同種の「分岐の多い純粋関数」で、既存の境界基準に合致する。
対象外のまま
handler.go系(works.Handlerの各Echoハンドラ)、worker.go(transcode.Worker.Work)、プール直結ラッパー(uploads.createPendingUpload・completeTUSUpload)は既存の境界どおりユニットテスト対象外のまま。transcodeAudio自体(ffmpeg実行・S3 I/O)もユニットテスト対象外のまま。