Phases 5, 6, 8: OCI pull/push paths and admin REST API

Phase 5 (OCI pull): internal/oci/ package with manifest GET/HEAD by
tag/digest, blob GET/HEAD with repo membership check, tag listing with
OCI pagination, catalog listing. Multi-segment repo names via
parseOCIPath() right-split routing. DB query layer in
internal/db/repository.go.

Phase 6 (OCI push): blob uploads (monolithic and chunked) with
uploadManager tracking in-progress BlobWriters, manifest push
implementing full ARCHITECTURE.md §5 flow in a single SQLite
transaction (create repo, upsert manifest, populate manifest_blobs,
atomic tag move). Digest verification on both blob commit and manifest
push-by-digest.

Phase 8 (admin REST): /v1 endpoints for auth (login/logout/health),
repository management (list/detail/delete), policy CRUD with engine
reload, audit log listing with filters, GC trigger/status stubs.
RequireAdmin middleware, platform-standard error format.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-19 18:25:18 -07:00
parent f5e67bd4aa
commit dddc66f31b
40 changed files with 6832 additions and 7 deletions

View File

@@ -0,0 +1,429 @@
package db
import (
"errors"
"testing"
)
// seedTestRepo inserts a repository, manifest, tag, blob, and manifest_blob
// link for use in repository query tests. It returns the repository ID.
func seedTestRepo(t *testing.T, d *DB) int64 {
t.Helper()
_, err := d.Exec(`INSERT INTO repositories (name) VALUES ('myorg/myapp')`)
if err != nil {
t.Fatalf("insert repo: %v", err)
}
var repoID int64
if err := d.QueryRow(`SELECT id FROM repositories WHERE name = 'myorg/myapp'`).Scan(&repoID); err != nil {
t.Fatalf("select repo id: %v", err)
}
_, err = d.Exec(
`INSERT INTO manifests (repository_id, digest, media_type, content, size)
VALUES (?, 'sha256:aaaa', 'application/vnd.oci.image.manifest.v1+json', '{"layers":[]}', 15)`,
repoID,
)
if err != nil {
t.Fatalf("insert manifest: %v", err)
}
var manifestID int64
if err := d.QueryRow(`SELECT id FROM manifests WHERE digest = 'sha256:aaaa'`).Scan(&manifestID); err != nil {
t.Fatalf("select manifest id: %v", err)
}
_, err = d.Exec(
`INSERT INTO tags (repository_id, name, manifest_id) VALUES (?, 'latest', ?)`,
repoID, manifestID,
)
if err != nil {
t.Fatalf("insert tag: %v", err)
}
_, err = d.Exec(`INSERT INTO blobs (digest, size) VALUES ('sha256:bbbb', 2048)`)
if err != nil {
t.Fatalf("insert blob: %v", err)
}
var blobID int64
if err := d.QueryRow(`SELECT id FROM blobs WHERE digest = 'sha256:bbbb'`).Scan(&blobID); err != nil {
t.Fatalf("select blob id: %v", err)
}
_, err = d.Exec(
`INSERT INTO manifest_blobs (manifest_id, blob_id) VALUES (?, ?)`,
manifestID, blobID,
)
if err != nil {
t.Fatalf("insert manifest_blob: %v", err)
}
return repoID
}
func TestGetRepositoryByName_Found(t *testing.T) {
d := openTestDB(t)
if err := d.Migrate(); err != nil {
t.Fatalf("Migrate: %v", err)
}
seedTestRepo(t, d)
id, err := d.GetRepositoryByName("myorg/myapp")
if err != nil {
t.Fatalf("GetRepositoryByName: %v", err)
}
if id == 0 {
t.Fatal("expected non-zero repository ID")
}
}
func TestGetRepositoryByName_NotFound(t *testing.T) {
d := openTestDB(t)
if err := d.Migrate(); err != nil {
t.Fatalf("Migrate: %v", err)
}
_, err := d.GetRepositoryByName("nonexistent")
if !errors.Is(err, ErrRepoNotFound) {
t.Fatalf("expected ErrRepoNotFound, got %v", err)
}
}
func TestGetManifestByTag_Found(t *testing.T) {
d := openTestDB(t)
if err := d.Migrate(); err != nil {
t.Fatalf("Migrate: %v", err)
}
repoID := seedTestRepo(t, d)
m, err := d.GetManifestByTag(repoID, "latest")
if err != nil {
t.Fatalf("GetManifestByTag: %v", err)
}
if m.Digest != "sha256:aaaa" {
t.Fatalf("digest: got %q, want %q", m.Digest, "sha256:aaaa")
}
if m.MediaType != "application/vnd.oci.image.manifest.v1+json" {
t.Fatalf("media type: got %q, want OCI manifest", m.MediaType)
}
if m.Size != 15 {
t.Fatalf("size: got %d, want 15", m.Size)
}
if string(m.Content) != `{"layers":[]}` {
t.Fatalf("content: got %q, want {\"layers\":[]}", string(m.Content))
}
}
func TestGetManifestByTag_NotFound(t *testing.T) {
d := openTestDB(t)
if err := d.Migrate(); err != nil {
t.Fatalf("Migrate: %v", err)
}
repoID := seedTestRepo(t, d)
_, err := d.GetManifestByTag(repoID, "v0.0.0-nonexistent")
if !errors.Is(err, ErrManifestNotFound) {
t.Fatalf("expected ErrManifestNotFound, got %v", err)
}
}
func TestGetManifestByDigest_Found(t *testing.T) {
d := openTestDB(t)
if err := d.Migrate(); err != nil {
t.Fatalf("Migrate: %v", err)
}
repoID := seedTestRepo(t, d)
m, err := d.GetManifestByDigest(repoID, "sha256:aaaa")
if err != nil {
t.Fatalf("GetManifestByDigest: %v", err)
}
if m.Digest != "sha256:aaaa" {
t.Fatalf("digest: got %q, want %q", m.Digest, "sha256:aaaa")
}
if m.RepositoryID != repoID {
t.Fatalf("repository_id: got %d, want %d", m.RepositoryID, repoID)
}
}
func TestGetManifestByDigest_NotFound(t *testing.T) {
d := openTestDB(t)
if err := d.Migrate(); err != nil {
t.Fatalf("Migrate: %v", err)
}
repoID := seedTestRepo(t, d)
_, err := d.GetManifestByDigest(repoID, "sha256:nonexistent")
if !errors.Is(err, ErrManifestNotFound) {
t.Fatalf("expected ErrManifestNotFound, got %v", err)
}
}
func TestBlobExistsInRepo_Exists(t *testing.T) {
d := openTestDB(t)
if err := d.Migrate(); err != nil {
t.Fatalf("Migrate: %v", err)
}
repoID := seedTestRepo(t, d)
exists, err := d.BlobExistsInRepo(repoID, "sha256:bbbb")
if err != nil {
t.Fatalf("BlobExistsInRepo: %v", err)
}
if !exists {
t.Fatal("expected blob to exist in repo")
}
}
func TestBlobExistsInRepo_NotInThisRepo(t *testing.T) {
d := openTestDB(t)
if err := d.Migrate(); err != nil {
t.Fatalf("Migrate: %v", err)
}
seedTestRepo(t, d) // creates blob sha256:bbbb in myorg/myapp
// Create a second repo with no manifests linking the blob.
_, err := d.Exec(`INSERT INTO repositories (name) VALUES ('other/repo')`)
if err != nil {
t.Fatalf("insert other repo: %v", err)
}
var otherRepoID int64
if err := d.QueryRow(`SELECT id FROM repositories WHERE name = 'other/repo'`).Scan(&otherRepoID); err != nil {
t.Fatalf("select other repo id: %v", err)
}
exists, err := d.BlobExistsInRepo(otherRepoID, "sha256:bbbb")
if err != nil {
t.Fatalf("BlobExistsInRepo: %v", err)
}
if exists {
t.Fatal("expected blob to NOT exist in other repo")
}
}
func TestBlobExistsInRepo_BlobDoesNotExist(t *testing.T) {
d := openTestDB(t)
if err := d.Migrate(); err != nil {
t.Fatalf("Migrate: %v", err)
}
repoID := seedTestRepo(t, d)
exists, err := d.BlobExistsInRepo(repoID, "sha256:nonexistent")
if err != nil {
t.Fatalf("BlobExistsInRepo: %v", err)
}
if exists {
t.Fatal("expected blob to not exist")
}
}
func TestListTags_WithTags(t *testing.T) {
d := openTestDB(t)
if err := d.Migrate(); err != nil {
t.Fatalf("Migrate: %v", err)
}
repoID := seedTestRepo(t, d)
// Add more tags pointing to the same manifest.
var manifestID int64
if err := d.QueryRow(`SELECT id FROM manifests WHERE repository_id = ?`, repoID).Scan(&manifestID); err != nil {
t.Fatalf("select manifest id: %v", err)
}
for _, tag := range []string{"v1.0", "v2.0", "beta"} {
_, err := d.Exec(`INSERT INTO tags (repository_id, name, manifest_id) VALUES (?, ?, ?)`,
repoID, tag, manifestID)
if err != nil {
t.Fatalf("insert tag %q: %v", tag, err)
}
}
tags, err := d.ListTags(repoID, "", 100)
if err != nil {
t.Fatalf("ListTags: %v", err)
}
// Expect alphabetical: beta, latest, v1.0, v2.0
want := []string{"beta", "latest", "v1.0", "v2.0"}
if len(tags) != len(want) {
t.Fatalf("tags count: got %d, want %d", len(tags), len(want))
}
for i, tag := range tags {
if tag != want[i] {
t.Fatalf("tags[%d]: got %q, want %q", i, tag, want[i])
}
}
}
func TestListTags_Pagination(t *testing.T) {
d := openTestDB(t)
if err := d.Migrate(); err != nil {
t.Fatalf("Migrate: %v", err)
}
repoID := seedTestRepo(t, d)
var manifestID int64
if err := d.QueryRow(`SELECT id FROM manifests WHERE repository_id = ?`, repoID).Scan(&manifestID); err != nil {
t.Fatalf("select manifest id: %v", err)
}
for _, tag := range []string{"v1.0", "v2.0", "beta"} {
_, err := d.Exec(`INSERT INTO tags (repository_id, name, manifest_id) VALUES (?, ?, ?)`,
repoID, tag, manifestID)
if err != nil {
t.Fatalf("insert tag %q: %v", tag, err)
}
}
// First page: 2 tags starting from beginning.
tags, err := d.ListTags(repoID, "", 2)
if err != nil {
t.Fatalf("ListTags page 1: %v", err)
}
if len(tags) != 2 {
t.Fatalf("page 1 count: got %d, want 2", len(tags))
}
if tags[0] != "beta" || tags[1] != "latest" {
t.Fatalf("page 1: got %v, want [beta, latest]", tags)
}
// Second page: after "latest".
tags, err = d.ListTags(repoID, "latest", 2)
if err != nil {
t.Fatalf("ListTags page 2: %v", err)
}
if len(tags) != 2 {
t.Fatalf("page 2 count: got %d, want 2", len(tags))
}
if tags[0] != "v1.0" || tags[1] != "v2.0" {
t.Fatalf("page 2: got %v, want [v1.0, v2.0]", tags)
}
// Third page: after "v2.0" — no more tags.
tags, err = d.ListTags(repoID, "v2.0", 2)
if err != nil {
t.Fatalf("ListTags page 3: %v", err)
}
if len(tags) != 0 {
t.Fatalf("page 3 count: got %d, want 0", len(tags))
}
}
func TestListTags_Empty(t *testing.T) {
d := openTestDB(t)
if err := d.Migrate(); err != nil {
t.Fatalf("Migrate: %v", err)
}
// Create a repo with no tags.
_, err := d.Exec(`INSERT INTO repositories (name) VALUES ('empty/repo')`)
if err != nil {
t.Fatalf("insert repo: %v", err)
}
var repoID int64
if err := d.QueryRow(`SELECT id FROM repositories WHERE name = 'empty/repo'`).Scan(&repoID); err != nil {
t.Fatalf("select repo id: %v", err)
}
tags, err := d.ListTags(repoID, "", 100)
if err != nil {
t.Fatalf("ListTags: %v", err)
}
if tags != nil {
t.Fatalf("expected nil tags, got %v", tags)
}
}
func TestListRepositoryNames_WithRepos(t *testing.T) {
d := openTestDB(t)
if err := d.Migrate(); err != nil {
t.Fatalf("Migrate: %v", err)
}
for _, name := range []string{"charlie/app", "alpha/lib", "bravo/svc"} {
_, err := d.Exec(`INSERT INTO repositories (name) VALUES (?)`, name)
if err != nil {
t.Fatalf("insert repo %q: %v", name, err)
}
}
names, err := d.ListRepositoryNames("", 100)
if err != nil {
t.Fatalf("ListRepositoryNames: %v", err)
}
want := []string{"alpha/lib", "bravo/svc", "charlie/app"}
if len(names) != len(want) {
t.Fatalf("names count: got %d, want %d", len(names), len(want))
}
for i, n := range names {
if n != want[i] {
t.Fatalf("names[%d]: got %q, want %q", i, n, want[i])
}
}
}
func TestListRepositoryNames_Pagination(t *testing.T) {
d := openTestDB(t)
if err := d.Migrate(); err != nil {
t.Fatalf("Migrate: %v", err)
}
for _, name := range []string{"charlie/app", "alpha/lib", "bravo/svc"} {
_, err := d.Exec(`INSERT INTO repositories (name) VALUES (?)`, name)
if err != nil {
t.Fatalf("insert repo %q: %v", name, err)
}
}
// First page: 2.
names, err := d.ListRepositoryNames("", 2)
if err != nil {
t.Fatalf("ListRepositoryNames page 1: %v", err)
}
if len(names) != 2 {
t.Fatalf("page 1 count: got %d, want 2", len(names))
}
if names[0] != "alpha/lib" || names[1] != "bravo/svc" {
t.Fatalf("page 1: got %v", names)
}
// Second page: after "bravo/svc".
names, err = d.ListRepositoryNames("bravo/svc", 2)
if err != nil {
t.Fatalf("ListRepositoryNames page 2: %v", err)
}
if len(names) != 1 {
t.Fatalf("page 2 count: got %d, want 1", len(names))
}
if names[0] != "charlie/app" {
t.Fatalf("page 2: got %v", names)
}
}
func TestListRepositoryNames_Empty(t *testing.T) {
d := openTestDB(t)
if err := d.Migrate(); err != nil {
t.Fatalf("Migrate: %v", err)
}
names, err := d.ListRepositoryNames("", 100)
if err != nil {
t.Fatalf("ListRepositoryNames: %v", err)
}
if names != nil {
t.Fatalf("expected nil names, got %v", names)
}
}