package mysql import ( "context" "fmt" "testing" "time" "gorm.io/driver/sqlite" "gorm.io/gorm" "wucher/internal/domain/federal_state" "wucher/internal/domain/icao" "wucher/internal/shared/pkg/uuidv7" ) func openICAOTestDB(t *testing.T) *gorm.DB { t.Helper() dsn := fmt.Sprintf("file:icao_test_%d?mode=memory&cache=shared", time.Now().UnixNano()) db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{ NowFunc: func() time.Time { return time.Now().UTC() }, }) if err != nil { t.Fatalf("open sqlite: %v", err) } if err := db.AutoMigrate(&icao.ICAO{}); err != nil { t.Fatalf("auto migrate: %v", err) } return db } func TestNewICAORepository(t *testing.T) { db := openICAOTestDB(t) repo := NewICAORepository(db) if repo == nil || repo.db == nil { t.Fatalf("expected repository initialized") } } func TestICAORepositoryCreate(t *testing.T) { db := openICAOTestDB(t) repo := NewICAORepository(db) row := &icao.ICAO{ICAOCode: "EDDB", Address: "Berlin"} if err := repo.Create(context.Background(), row); err != nil { t.Fatalf("unexpected error: %v", err) } if len(row.ID) == 0 { t.Fatalf("expected id set") } inactive := &icao.ICAO{ICAOCode: "EDIN", IsActive: false} if err := repo.Create(context.Background(), inactive); err != nil { t.Fatalf("create inactive: %v", err) } gotInactive, err := repo.GetByID(context.Background(), inactive.ID) if err != nil { t.Fatalf("get inactive: %v", err) } if gotInactive == nil || gotInactive.IsActive { t.Fatalf("expected inactive icao persisted as false") } sqlDB, _ := db.DB() _ = sqlDB.Close() if err := repo.Create(context.Background(), &icao.ICAO{ICAOCode: "EDDM"}); err == nil { t.Fatalf("expected error on closed DB") } } func TestICAORepositoryUpdate(t *testing.T) { db := openICAOTestDB(t) repo := NewICAORepository(db) row := &icao.ICAO{ICAOCode: "EDDF", Address: "Old"} if err := repo.Create(context.Background(), row); err != nil { t.Fatalf("seed create: %v", err) } row.ICAOCode = "EDDH" if err := repo.Update(context.Background(), row); err != nil { t.Fatalf("unexpected error: %v", err) } loaded, err := repo.GetByID(context.Background(), row.ID) if err != nil || loaded == nil || loaded.ICAOCode != "EDDH" { t.Fatalf("expected updated row") } sqlDB, _ := db.DB() _ = sqlDB.Close() if err := repo.Update(context.Background(), row); err == nil { t.Fatalf("expected error on closed DB") } } func TestICAORepositoryUpdateOmitsFederalStateAssociation(t *testing.T) { db := openICAOTestDB(t) repo := NewICAORepository(db) row := &icao.ICAO{ ICAOCode: "EDDF", FederalStateID: uuidv7.MustBytes(), } if err := repo.Create(context.Background(), row); err != nil { t.Fatalf("seed create: %v", err) } newFederalStateID := uuidv7.MustBytes() row.FederalStateID = newFederalStateID row.FederalState = &federal_state.FederalState{ ID: uuidv7.MustBytes(), Name: "stale preload relation", } if err := repo.Update(context.Background(), row); err != nil { t.Fatalf("unexpected update error with stale relation: %v", err) } loaded, err := repo.GetByID(context.Background(), row.ID) if err != nil { t.Fatalf("reload: %v", err) } if loaded == nil { t.Fatalf("expected row") } if string(loaded.FederalStateID) != string(newFederalStateID) { t.Fatalf("expected federal_state_id updated") } } func TestICAORepositoryDelete(t *testing.T) { db := openICAOTestDB(t) repo := NewICAORepository(db) row := &icao.ICAO{ICAOCode: "EDDK"} if err := repo.Create(context.Background(), row); err != nil { t.Fatalf("seed create: %v", err) } deletedBy := uuidv7.MustBytes() if err := repo.Delete(context.Background(), row.ID, deletedBy); err != nil { t.Fatalf("unexpected error: %v", err) } got, err := repo.GetByID(context.Background(), row.ID) if err != nil { t.Fatalf("get by id after delete: %v", err) } if got != nil { t.Fatalf("expected soft deleted row hidden from GetByID") } } func TestICAORepositoryGetByID(t *testing.T) { t.Run("found", func(t *testing.T) { db := openICAOTestDB(t) repo := NewICAORepository(db) row := &icao.ICAO{ICAOCode: "EDDL"} if err := repo.Create(context.Background(), row); err != nil { t.Fatalf("seed create: %v", err) } got, err := repo.GetByID(context.Background(), row.ID) if err != nil { t.Fatalf("unexpected error: %v", err) } if got == nil || got.ICAOCode != "EDDL" { t.Fatalf("expected row found") } }) t.Run("not found", func(t *testing.T) { db := openICAOTestDB(t) repo := NewICAORepository(db) got, err := repo.GetByID(context.Background(), uuidv7.MustBytes()) if err != nil { t.Fatalf("unexpected error: %v", err) } if got != nil { t.Fatalf("expected nil for not found") } }) t.Run("db error", func(t *testing.T) { db := openICAOTestDB(t) repo := NewICAORepository(db) sqlDB, _ := db.DB() _ = sqlDB.Close() if _, err := repo.GetByID(context.Background(), uuidv7.MustBytes()); err == nil { t.Fatalf("expected error on closed DB") } }) } func TestICAORepositoryList(t *testing.T) { t.Run("success without limit", func(t *testing.T) { db := openICAOTestDB(t) repo := NewICAORepository(db) _ = repo.Create(context.Background(), &icao.ICAO{ICAOCode: "C", Address: "Gamma"}) _ = repo.Create(context.Background(), &icao.ICAO{ICAOCode: "A", Address: "Alpha"}) rows, total, err := repo.List(context.Background(), "", "", 0, 0) if err != nil { t.Fatalf("unexpected error: %v", err) } if total != 2 || len(rows) != 2 { t.Fatalf("expected 2 rows, total=%d len=%d", total, len(rows)) } }) t.Run("success with filter sort and limit", func(t *testing.T) { db := openICAOTestDB(t) repo := NewICAORepository(db) _ = repo.Create(context.Background(), &icao.ICAO{ICAOCode: "Main Base", Address: "Center"}) _ = repo.Create(context.Background(), &icao.ICAO{ICAOCode: "Backup", Address: "Secondary"}) rows, total, err := repo.List(context.Background(), "Main", "icao_code DESC", 1, 0) if err != nil { t.Fatalf("unexpected error: %v", err) } if total != 1 || len(rows) != 1 || rows[0].ICAOCode != "Main Base" { t.Fatalf("unexpected list result") } }) t.Run("count error", func(t *testing.T) { db := openICAOTestDB(t) repo := NewICAORepository(db) if err := db.Migrator().DropTable(&icao.ICAO{}); err != nil { t.Fatalf("drop table: %v", err) } if _, _, err := repo.List(context.Background(), "", "", 10, 0); err == nil { t.Fatalf("expected count error") } }) t.Run("find error", func(t *testing.T) { db := openICAOTestDB(t) repo := NewICAORepository(db) _ = repo.Create(context.Background(), &icao.ICAO{ICAOCode: "Main"}) if _, _, err := repo.List(context.Background(), "", "icao_code ASC, )", 10, 0); err == nil { t.Fatalf("expected find error from invalid sort") } }) t.Run("default order active sortkey first and inactive last", func(t *testing.T) { db := openICAOTestDB(t) repo := NewICAORepository(db) _ = repo.Create(context.Background(), &icao.ICAO{ICAOCode: "Gamma", IsActive: true}) _ = repo.Create(context.Background(), &icao.ICAO{ICAOCode: "Beta", SortKey: intPtrICAORepo(0), IsActive: true}) _ = repo.Create(context.Background(), &icao.ICAO{ICAOCode: "Charlie", SortKey: intPtrICAORepo(2), IsActive: true}) _ = repo.Create(context.Background(), &icao.ICAO{ICAOCode: "Alpha", SortKey: intPtrICAORepo(1), IsActive: true}) _ = repo.Create(context.Background(), &icao.ICAO{ICAOCode: "Zulu", IsActive: false}) _ = repo.Create(context.Background(), &icao.ICAO{ICAOCode: "Bravo", SortKey: intPtrICAORepo(9), IsActive: false}) rows, total, err := repo.List(context.Background(), "", "", 0, 0) if err != nil { t.Fatalf("unexpected error: %v", err) } if total != 6 || len(rows) != 6 { t.Fatalf("unexpected total/len total=%d len=%d", total, len(rows)) } gotOrder := []string{rows[0].ICAOCode, rows[1].ICAOCode, rows[2].ICAOCode, rows[3].ICAOCode, rows[4].ICAOCode, rows[5].ICAOCode} wantOrder := []string{"Beta", "Alpha", "Charlie", "Gamma", "Bravo", "Zulu"} for i := range wantOrder { if gotOrder[i] != wantOrder[i] { t.Fatalf("unexpected default order: got=%v want=%v", gotOrder, wantOrder) } } }) } func intPtrICAORepo(v int) *int { return &v }