Skip to content

E2E検証で見つかった実バグの記録

e2e-smoke-testingscripts/e2e-smoke.sh)を実際に走らせて見つかった、インフラ・外部ライブラリの実挙動起因のバグの記録。いずれもユニットテスト(fakeベース)では検出不可能な種類の問題だった。

1. 001_river.sqlのCREATE TYPE/TABLE順序バグ

backend/db/schema/001_river.sqlで、river_jobテーブル定義(river_job_state型を使用)が、その型自体のCREATE TYPE river_job_state宣言よりに書かれていた。Postgresは型定義前の参照を許さないため、まっさらなデータディレクトリから初期化すると必ず失敗する。

以前のPostgresデータディレクトリ(backend/postgres、後に.data/postgresへ移動)が既に不完全な状態で初期化済みだったため、スキーマが再適用されることがなく長らく気づかれていなかった。.data/postgresをリセットして初期化し直したところ、postgresコンテナのログでtype "river_job_state" does not existとして顕在化した。

修正: CREATE TYPECREATE TABLEより前に移動。

2. TUS所有者チェックがtusd(S3store)の複合IDで機能しない

backend/internal/uploads/tus_handler.goRequireOwnerミドルウェアは、リクエストパスの先頭セグメントをそのままuploads.idとしてDB照会していた。ところがtusdのS3ストア実装(github.com/tus/tusd/v2/pkg/s3store)は、アプリ側が割り当てたIDに+<S3マルチパートアップロードID>を連結した複合ID(例: 14289298-...+53afe0f9...)を実際のリソースIDとして使う仕様だった(s3store.gosplitIds関数参照)。

DBのuploads.idにはアプリが割り当てた素のUUIDしか保存されていないため、複合IDのままlookupすると常に失敗し、本人であっても404になっていた(所有者チェック機能そのものが実質機能しない状態)。ユニットテスト(requireUploadOwner単体)は素のuploadIDを直接渡していたため、この不整合を検出できなかった。

修正: tusdのsplitIdsと同じロジック(strings.LastIndex(id, "+")で分割)をobjectIDFromPathとして実装し、DB照会前に複合IDから素のuploadIDを取り出すようにした。回帰テストTestObjectIDFromPathbackend/internal/uploads/tus_handler_test.go)を追加。

3. CompleteUploadsフックも同じ複合IDの問題を持っていた(transcodeマイルストーンで発覚)

2と同じ複合ID問題が、別の経路にも存在していた。backend/cmd/app/main.goCompleteUploadsチャンネル受信ループが、tusdのフックイベントが持つevent.Upload.ID(=複合ID)をそのままHandler.CompleteTUSUploadに渡していたため、SetUploadTranscodingが対象行を見つけられずno rows in result setで失敗し、アップロードが完了してもtranscodeジョブが一切積まれない状態になっていた。HTTPレスポンス自体は正常(204)を返すため、クライアント側からは成功しているように見え、サーバーログのfailed to complete upload ...という1行だけが唯一の手がかりだった。

これは2の修正(RequireOwner向けのobjectIDFromPath)だけでは直っておらず、同じ「複合IDをどこで剥がすか」という問題が呼び出し経路ごとに個別に潜んでいたことを示している。今回のOpus変換マイルストーンでscripts/e2e-smoke.shに実際の変換完了(uploads.status=done)を待つ検証を追加したことで発見した。

修正: 複合ID剥がしのロジックをstripMultipartSuffixという共通関数に切り出し、objectIDFromPathHandler.CompleteTUSUploadの両方から使うようにした。回帰テストTestStripMultipartSuffixを追加。

教訓

fakeベースのユニットテストは「書いたロジックが仕様通りか」は検証できるが、「外部ライブラリ・インフラが実際にどう振る舞うか」の思い込みまでは検証できない。今回の3件はどれも、ユニットテストは全てgreenのまま実運用で初めて顕在化する類のバグだった。特に2と3は、同じ根本原因(tusdの複合ID)が別々の呼び出し経路に個別に存在していた例で、「1箇所直したから大丈夫」と判断せず、同種の呼び出し経路を横断的に洗い出す・共通ヘルパーに一本化することの重要性を示している。TDDのユニットテストとE2Eスモークテストは役割が異なり、両方が必要という実例。