package repository_test import ( "context" "os" "testing" "osdb/internal/database" "osdb/internal/domain" "osdb/internal/handler" "osdb/internal/repository" ) // pngFixture is a minimal valid PNG for byte round-trip testing. var pngFixture = []byte{ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x00, 0x00, 0x00, 0x90, 0x77, 0x53, 0xde, 0x00, 0x00, 0x00, 0x0c, 0x49, 0x44, 0x41, 0x54, 0x08, 0xd7, 0x63, 0xf8, 0xcf, 0xc0, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0xe2, 0x21, 0xbc, 0x33, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82, } func TestFruitRepoIntegration(t *testing.T) { dsn := os.Getenv("DATABASE_URL") if dsn == "" { t.Skip("DATABASE_URL not set — skipping integration test") } ctx := context.Background() pool, err := database.Connect(ctx, dsn) if err != nil { t.Fatalf("connect: %v", err) } defer pool.Close() repo := repository.NewFruitRepo(pool) // cleanup after test t.Cleanup(func() { pool.Exec(ctx, `DELETE FROM fruits WHERE osdb_number LIKE 'TEST-%'`) }) // Create dto := domain.FruitWriteDTO{ Name: "Boskop", OSDBNumber: "TEST-001", FruitType: "Apfelsorten", Synonyms: []string{"Boskop-Renette", "Schöner aus Boskoop"}, } f, err := repo.Create(ctx, dto) if err != nil { t.Fatalf("Create: %v", err) } if f.ID == 0 { t.Fatal("want non-zero ID") } if len(f.Synonyms) != 2 { t.Fatalf("want 2 synonyms got %d", len(f.Synonyms)) } // Duplicate osdb_number → ErrDuplicateOSDBNumber _, err = repo.Create(ctx, dto) if err != handler.ErrDuplicateOSDBNumber { t.Fatalf("want ErrDuplicateOSDBNumber got %v", err) } // Get (synonyms present) got, err := repo.Get(ctx, f.ID) if err != nil { t.Fatalf("Get: %v", err) } if len(got.Synonyms) != 2 { t.Fatalf("want 2 synonyms got %d", len(got.Synonyms)) } // Update (replaces synonyms wholesale) updated, err := repo.Update(ctx, f.ID, domain.FruitWriteDTO{ Name: "Boskop Updated", OSDBNumber: "TEST-001", FruitType: "Apfelsorten", Synonyms: []string{"Renamed"}, }) if err != nil { t.Fatalf("Update: %v", err) } if updated.Name != "Boskop Updated" { t.Fatalf("want updated name got %s", updated.Name) } if len(updated.Synonyms) != 1 || updated.Synonyms[0] != "Renamed" { t.Fatalf("want [Renamed] got %v", updated.Synonyms) } // List fruits, total, err := repo.List(ctx, 50, 0) if err != nil { t.Fatalf("List: %v", err) } if total < 1 { t.Fatalf("want total >= 1 got %d", total) } _ = fruits // AddImage + GetImageData byte round-trip filename := "test.png" imageType := "fruit" img, err := repo.AddImage(ctx, f.ID, &filename, pngFixture, imageType, nil) if err != nil { t.Fatalf("AddImage: %v", err) } if img.ID == 0 { t.Fatal("want non-zero image ID") } data, err := repo.GetImageData(ctx, f.ID, img.ID) if err != nil { t.Fatalf("GetImageData: %v", err) } if len(data) != len(pngFixture) { t.Fatalf("byte round-trip: want %d bytes got %d", len(pngFixture), len(data)) } // DeleteImage if err := repo.DeleteImage(ctx, f.ID, img.ID); err != nil { t.Fatalf("DeleteImage: %v", err) } if _, err := repo.GetImageData(ctx, f.ID, img.ID); err != handler.ErrNotFound { t.Fatalf("after delete: want ErrNotFound got %v", err) } // Delete (cascade check) if err := repo.Delete(ctx, f.ID); err != nil { t.Fatalf("Delete: %v", err) } if _, err := repo.Get(ctx, f.ID); err != handler.ErrNotFound { t.Fatalf("after delete: want ErrNotFound got %v", err) } // Update non-existent → ErrNotFound if _, err := repo.Update(ctx, 999999, dto); err != handler.ErrNotFound { t.Fatalf("Update non-existent: want ErrNotFound got %v", err) } }