test: raise core-package unit coverage to 90% (#98)
Raises statement coverage of the core packages (all of `internal/` except the interactive `tui/`, plus `pkg/`) from **8.7% to 90.1%**. ## Approach - **Pure-go unit tests** for all providers, virtual mergers, classifier, config, auth, models, and the API client (httptest). - **Testcontainers-backed** tests (new `internal/testsupport` helper: Postgres/Redis/MinIO, Ryuk disabled) for database, storage, cache, the proxy engine, the GC, and a full-stack `server` test that drives the whole HTTP API. These `t.Skip` when Docker is absent so `go test` still runs locally without it. ## Measuring ``` go test -coverpkg=./internal/...,./pkg/... -coverprofile=cover.out ./internal/... ./pkg/... grep -v /internal/tui/ cover.out | go tool cover -func=/dev/stdin | tail -1 # 90.1% ``` Run with `-p 1` (containers are heavy). ## Notes - The interactive `tui/` package and `cmd/main` are excluded from the target per the agreed scope. - Some defensive error branches are covered via fault injection (closed DB pool, killing MinIO mid-upload). Reviewed-on: #98 Co-authored-by: Ben Vincent <ben@unkin.net> Co-committed-by: Ben Vincent <ben@unkin.net>
This commit was merged in pull request #98.
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
package gc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.unkin.net/unkin/artifactapi/internal/database"
|
||||
"git.unkin.net/unkin/artifactapi/internal/storage"
|
||||
"git.unkin.net/unkin/artifactapi/internal/testsupport"
|
||||
)
|
||||
|
||||
var (
|
||||
testDB *database.DB
|
||||
testStore *storage.S3
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
ctx := context.Background()
|
||||
dsn, termPG, err := testsupport.StartPostgres(ctx)
|
||||
if err != nil {
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
minio, termMinio, err := testsupport.StartMinio(ctx)
|
||||
if err != nil {
|
||||
termPG()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
db, err := database.New(dsn)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var s3 *storage.S3
|
||||
for i := 0; i < 20; i++ {
|
||||
if s3, err = storage.NewS3(minio.Endpoint, minio.AccessKey, minio.SecretKey, "gc-test", false, ""); err == nil {
|
||||
break
|
||||
}
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
testDB = db
|
||||
testStore = s3
|
||||
|
||||
code := m.Run()
|
||||
db.Close()
|
||||
termMinio()
|
||||
termPG()
|
||||
if code != 0 {
|
||||
os.Exit(code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSweepDeletesOldOrphan(t *testing.T) {
|
||||
if testDB == nil {
|
||||
t.Skip("Docker unavailable")
|
||||
}
|
||||
ctx := context.Background()
|
||||
hash := "sha256:gcorphan"
|
||||
key := storage.BlobKey("gcorphan")
|
||||
|
||||
if err := testStore.Upload(ctx, key, bytes.NewReader([]byte("orphan")), 6, "application/octet-stream"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := testDB.UpsertBlob(ctx, hash, key, 6, "application/octet-stream"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Age the blob past the grace period.
|
||||
if _, err := testDB.Pool.Exec(ctx, `UPDATE blobs SET created_at = now() - interval '2 hours' WHERE content_hash = $1`, hash); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
c := New(testDB, testStore, time.Hour)
|
||||
c.sweep(ctx)
|
||||
|
||||
if exists, _ := testStore.Exists(ctx, key); exists {
|
||||
t.Error("expected orphan object deleted from store")
|
||||
}
|
||||
orphans, _ := testDB.FindOrphanedBlobs(ctx, 0)
|
||||
for _, b := range orphans {
|
||||
if b.ContentHash == hash {
|
||||
t.Error("expected orphan blob row deleted")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSweepNoOrphans(t *testing.T) {
|
||||
if testDB == nil {
|
||||
t.Skip("Docker unavailable")
|
||||
}
|
||||
// A sweep with nothing to collect should be a clean no-op.
|
||||
New(testDB, testStore, time.Hour).sweep(context.Background())
|
||||
}
|
||||
|
||||
func TestRunStopsOnContextCancel(t *testing.T) {
|
||||
if testDB == nil {
|
||||
t.Skip("Docker unavailable")
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
New(testDB, testStore, time.Hour).Run(ctx)
|
||||
close(done)
|
||||
}()
|
||||
cancel()
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("Run did not return after context cancel")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user