package acquire import ( "context" "errors" "io" "log/slog" "os" "testing" "github.com/jackc/pgx/v5/pgxpool" "git.fabledsword.com/bvandeusen/minstrel/internal/db" "git.fabledsword.com/bvandeusen/minstrel/internal/dbtest" ) func newPool(t *testing.T) *pgxpool.Pool { t.Helper() if testing.Short() { t.Skip("skipping integration test in -short mode") } dsn := os.Getenv("MINSTREL_TEST_DATABASE_URL") if dsn == "" { t.Skip("MINSTREL_TEST_DATABASE_URL not set") } if err := db.Migrate(dsn, slog.New(slog.NewTextHandler(io.Discard, nil))); err != nil { t.Fatalf("migrate: %v", err) } pool, err := pgxpool.New(context.Background(), dsn) if err != nil { t.Fatalf("pool: %v", err) } t.Cleanup(pool.Close) dbtest.ResetDB(t, pool) return pool } func TestStore_AddsConfiguresAndRemoves_Integration(t *testing.T) { ctx := context.Background() s := NewStore(newPool(t)) first, err := s.Create(ctx, ProtocolExchange) if err != nil { t.Fatalf("create: %v", err) } second, err := s.Create(ctx, ProtocolExchange) if err != nil { t.Fatalf("create a second: %v", err) } if first.Code != "exchange" || second.Code != "exchange-2" || second.Label != "FabledExchange 2" { t.Errorf("codes = %q, %q (%q); want exchange, exchange-2", first.Code, second.Code, second.Label) } // A new exchange is off, does every job, files under minstrel and is // unpaced. if first.Enabled || first.Kind != KindDownload || !first.ServesSearch || !first.ServesDownloads || !first.ServesEnrichment || first.Category != DefaultCategory || first.SearchesPerHour != 0 || first.LocalPath != "/downloads" { t.Errorf("a new exchange = %+v", first) } if _, err := s.Create(ctx, "transmission"); !errors.As(err, new(*InvalidError)) { t.Errorf("an unknown protocol: err = %v", err) } u := Update{ Label: "Exchange", Enabled: true, BaseURL: "http://exchange:8080", Category: "minstrel", TimeoutMs: 20000, ServesSearch: true, LocalPath: "/downloads", RemotePath: "/data/torrents", } saved, err := s.Save(ctx, first.Code, u) if err != nil { t.Fatalf("save: %v", err) } if !saved.Enabled || saved.ServesDownloads || saved.RemotePath != "/data/torrents" { t.Errorf("saved = %+v", saved) } if _, err := s.Save(ctx, "nope", u); !errors.Is(err, ErrNotFound) { t.Errorf("saving a missing row: err = %v", err) } // Secrets are write-only: the list says whether one is set, never what. if err := s.SetAPIKey(ctx, first.Code, " key "); err != nil { t.Fatalf("set key: %v", err) } full, err := s.Get(ctx, first.Code) if err != nil || full.ApiKey != "key" { t.Errorf("stored key = %q (err %v), want it trimmed", full.ApiKey, err) } rows, err := s.List(ctx) if err != nil || len(rows) != 2 { t.Fatalf("list: %d rows, err %v", len(rows), err) } for _, r := range rows { if r.Code == first.Code && !r.HasApiKey { t.Error("the list should say a key is set") } } // Only the exchange with search switched on is a search source. srcs, err := Sources(ctx, s.pool) if err != nil || len(srcs) != 1 || srcs[0].Code != first.Code { t.Errorf("sources = %+v, err %v; want only the enabled exchange", srcs, err) } if err := s.Delete(ctx, second.Code); err != nil { t.Fatalf("delete: %v", err) } if err := s.Delete(ctx, second.Code); !errors.Is(err, ErrNotFound) { t.Errorf("deleting twice: err = %v", err) } }