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:
2026-07-03 14:31:24 +10:00
committed by BenVincent
parent 1b585af14e
commit a1ba86e76b
26 changed files with 3555 additions and 0 deletions
+101
View File
@@ -0,0 +1,101 @@
// Package testsupport starts throwaway backing containers (Postgres, Redis,
// MinIO) for integration-style unit tests. It is only ever imported from
// *_test.go files, so it never reaches the production binary. Each Start*
// function returns a connection detail plus a terminate func; callers wire
// them up in a TestMain and skip the package's tests when Docker is absent.
package testsupport
import (
"context"
"fmt"
"os"
"time"
"github.com/testcontainers/testcontainers-go"
tcpostgres "github.com/testcontainers/testcontainers-go/modules/postgres"
tcredis "github.com/testcontainers/testcontainers-go/modules/redis"
"github.com/testcontainers/testcontainers-go/wait"
)
func init() {
// The Ryuk reaper container cannot start in this environment; each Start*
// returns an explicit terminate func for cleanup instead.
if _, ok := os.LookupEnv("TESTCONTAINERS_RYUK_DISABLED"); !ok {
os.Setenv("TESTCONTAINERS_RYUK_DISABLED", "true")
}
}
// StartPostgres launches postgres:17-alpine and returns its DSN.
func StartPostgres(ctx context.Context) (dsn string, terminate func(), err error) {
c, err := tcpostgres.Run(ctx,
"postgres:17-alpine",
tcpostgres.WithDatabase("artifacts"),
tcpostgres.WithUsername("artifacts"),
tcpostgres.WithPassword("artifacts123"),
testcontainers.WithWaitStrategy(
// Postgres opens the port, runs init scripts, then restarts, so wait
// for the readiness log to appear twice to avoid connection resets.
wait.ForLog("database system is ready to accept connections").
WithOccurrence(2).
WithStartupTimeout(60*time.Second),
),
)
if err != nil {
return "", nil, err
}
host, _ := c.Host(ctx)
port, _ := c.MappedPort(ctx, "5432/tcp")
dsn = fmt.Sprintf("postgres://artifacts:artifacts123@%s:%s/artifacts?sslmode=disable", host, port.Port())
return dsn, func() { _ = c.Terminate(ctx) }, nil
}
// StartRedis launches redis:7-alpine and returns its URL.
func StartRedis(ctx context.Context) (url string, terminate func(), err error) {
c, err := tcredis.Run(ctx,
"redis:7-alpine",
testcontainers.WithWaitStrategy(
wait.ForListeningPort("6379/tcp").WithStartupTimeout(60*time.Second),
),
)
if err != nil {
return "", nil, err
}
host, _ := c.Host(ctx)
port, _ := c.MappedPort(ctx, "6379/tcp")
url = fmt.Sprintf("redis://%s:%s", host, port.Port())
return url, func() { _ = c.Terminate(ctx) }, nil
}
// MinioConn holds MinIO connection details.
type MinioConn struct {
Endpoint string
AccessKey string
SecretKey string
}
// StartMinio launches minio and returns its connection details.
func StartMinio(ctx context.Context) (conn MinioConn, terminate func(), err error) {
c, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ContainerRequest: testcontainers.ContainerRequest{
Image: "minio/minio:latest",
ExposedPorts: []string{"9000/tcp"},
Cmd: []string{"server", "/data"},
Env: map[string]string{
"MINIO_ROOT_USER": "minioadmin",
"MINIO_ROOT_PASSWORD": "minioadmin",
},
WaitingFor: wait.ForHTTP("/minio/health/ready").WithPort("9000/tcp").WithStartupTimeout(60 * time.Second),
},
Started: true,
})
if err != nil {
return MinioConn{}, nil, err
}
host, _ := c.Host(ctx)
port, _ := c.MappedPort(ctx, "9000/tcp")
return MinioConn{
Endpoint: fmt.Sprintf("%s:%s", host, port.Port()),
AccessKey: "minioadmin",
SecretKey: "minioadmin",
}, func() { _ = c.Terminate(ctx) }, nil
}