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) } }