Guard yang Bilang Tidak dan Jalan Pulang Migrasi
Commit test-only: pembuktian guard global via httptest dan jalur down migrasi di MySQL 5.7 beneran mundur lalu pulih.
Ringkasan
Ternyata file down migration sama middleware penolak nggak pernah ditest beneran. Sekarang guard-nya diuji pakai table-driven test, yang ALL lolos 200 tapi bidang lain kena 403 kecuali konteks kosong yang sengaja fail-open. Terus jalur down juga dibuktiin lewat tes integrasi yang nurunin migrasi ke 56 sampai kolomnya hilang lalu naik lagi sampai balik.
Nulis file down migration butuh waktu lima menit. Memercayainya butuh test. Faktanya, sampai kemarin nggak ada satu pun proses yang benar-benar menjalankan jalur down itu di environment kita.
Jujur, tebakan pertama saya: file down itu beban mati, formalitas biar tool-nya diem. Toh siapa yang sering rollback database? Anggapan itu yang bikin dua blind spot ini nggak tersentuh: guard yang tugasnya bilang tidak, dan jalan pulang yang belum pernah dilalui.
Guard yang bilang tidak
Middleware RequireGlobalBidang menjaga tabel data global (menu, settings, layanan pengaduan): hanya scope ALL yang boleh lewat; bidang dinas mana pun harus ditolak 403. Penolakan adalah fungsi, tapi penolakan yang nggak pernah diuji itu cuma harapan. Saya bikin table-driven test pakai httptest [6]: recorder plus request baru, lalu context CtxBidang disuntik langsung, tanpa server sungguhan. Empat kasus eksplisit: scope ALL dapat 200, bidang olahraga 403, bidang pariwisata 403, dan context kosong juga 200.
Kasus terakhir itu yang sering bikin kedip: context kosong lolos, fail-open by design. Fungsi BidangFrom memperlakukan ketiadaan context sebagai ALL, karena request publik dan data pra-migrasi-57 memang nggak bawa bidang. Kontrak ini sekarang dipaku test, jadi nggak ada yang bisa meluruskannya diam-diam dan merusak pemanggil yang sudah bergantung. Bentuk table-driven-nya juga memangkas kode repetitif; kasus baru cukup nambah baris di tabel [8].
Membuktikan jalan pulang
Test kedua lebih berat: TestMigrations_DownUp. Karena menyentuh MySQL beneran, dia dikunci di balik build tag //go:build integration; build constraint itu kondisi kapan sebuah file ikut masuk paket [11], jadi tes ini cuma ikut saat dipanggil eksplisit, nggak ikut unit run harian. Isinya bukan cuma mundur: dari head, m.Migrate(56) berjalan mundur empat langkah, 000060 ke 000059 ke 000058 ke 000057, urutan LIFO. Versi harus mentok di 56 dan status dirty-nya false.
Pembuktian sebenarnya lewat information_schema.COLUMNS: empat kolom penanda era 5B (users.bidang, entities.attributes, announcements.bidang, registration_submissions.bidang) harus hilang dari struktur. Setelah itu m.Up() menanam ulang seluruh rantai dari 56 ke head, dan keempat kolom harus balik. ErrNoChange ditoleransi [9] karena kondisi yang sudah di head boleh dianggap selesai. Bentuk intinya:
func TestMigrations_DownUp(t *testing.T) {
db := testutil.SetupTestDB(t)
m, err := migrate.New("file://../../migrations", "mysql://"+dsn)
if err != nil {
t.Fatalf("create migrate: %v", err)
}
if err := m.Migrate(56); err != nil {
t.Fatalf("migrate down to 56: %v", err)
}
if v, dirty, err := m.Version(); err != nil || v != 56 || dirty {
t.Fatalf("version after down: v=%d dirty=%v err=%v", v, dirty, err)
}
// information_schema.COLUMNS: 4 kolom 000057 harus hilang
// ... lalu naik lagi ke head:
if err := m.Up(); err != nil && err != migrate.ErrNoChange {
t.Fatalf("migrate up after down: %v", err)
}
}DSN-nya wajib bawa multiStatements=true; driver MySQL golang-migrate butuh itu supaya bisa mengeksekusi beberapa statement SQL dalam satu batch koneksi [10], dan tabel pelacak versinya default bernama schema_migrations [10]. Dua detail kecil bikin test ini nyambung ke kebiasaan yang sudah jalan: subtest t.Run menjaga nama tiap kasus tetap terbaca rapi di output [8], dan satu baris di INDEX.md membuat keduanya ketemu oleh runner yang bener: unit untuk guard, integration untuk migrasi.
Test biasanya tumbuh ke arah maju: fitur baru, happy path baru. Commit ini jalan ke arah lain: menambal dua sisi yang jarang dijamah. Guard yang tugasnya menolak kini punya kontrak tertulis, termasuk pengecualian fail-open-nya. Dan jalan pulang migrations kini sudah pernah dilalui, mundur sampai titik aman lalu balik lagi tanpa sisa. Bukan teori, bukan asumsi tim, tapi baris test yang bisa dijalankan siapa saja.