mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-08-29 12:17:00 +00:00
add and fix more query tests
This commit is contained in:
@@ -701,18 +701,18 @@ func GetManifest(db *sql.DB, digest string) (*Manifest, error) {
|
||||
var m Manifest
|
||||
|
||||
// Use sql.NullString for nullable annotation fields
|
||||
var title, description, sourceURL, documentationURL, licenses, iconURL sql.NullString
|
||||
var title, description, sourceURL, documentationURL, licenses, iconURL, readmeURL sql.NullString
|
||||
|
||||
err := db.QueryRow(`
|
||||
SELECT id, did, repository, digest, hold_endpoint, schema_version,
|
||||
media_type, config_digest, config_size, created_at,
|
||||
title, description, source_url, documentation_url, licenses, icon_url
|
||||
title, description, source_url, documentation_url, licenses, icon_url, readme_url
|
||||
FROM manifests
|
||||
WHERE digest = ?
|
||||
`, digest).Scan(&m.ID, &m.DID, &m.Repository, &m.Digest, &m.HoldEndpoint,
|
||||
&m.SchemaVersion, &m.MediaType, &m.ConfigDigest, &m.ConfigSize,
|
||||
&m.CreatedAt,
|
||||
&title, &description, &sourceURL, &documentationURL, &licenses, &iconURL)
|
||||
&title, &description, &sourceURL, &documentationURL, &licenses, &iconURL, &readmeURL)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -737,6 +737,9 @@ func GetManifest(db *sql.DB, digest string) (*Manifest, error) {
|
||||
if iconURL.Valid {
|
||||
m.IconURL = iconURL.String
|
||||
}
|
||||
if readmeURL.Valid {
|
||||
m.ReadmeURL = readmeURL.String
|
||||
}
|
||||
|
||||
return &m, nil
|
||||
}
|
||||
|
||||
@@ -118,3 +118,796 @@ func TestGetRepositoryMetadata(t *testing.T) {
|
||||
t.Error("Expected all empty strings for manifest with NULL metadata fields")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInsertManifest(t *testing.T) {
|
||||
// Create in-memory test database
|
||||
db, err := InitDB(":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to init database: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
// Insert test user
|
||||
testUser := &User{
|
||||
DID: "did:plc:test123",
|
||||
Handle: "testuser.bsky.social",
|
||||
PDSEndpoint: "https://test.pds.example.com",
|
||||
Avatar: "",
|
||||
LastSeen: time.Now(),
|
||||
}
|
||||
if err := UpsertUser(db, testUser); err != nil {
|
||||
t.Fatalf("Failed to insert user: %v", err)
|
||||
}
|
||||
|
||||
// Test 1: Insert new manifest with all fields populated
|
||||
manifest1 := &Manifest{
|
||||
DID: testUser.DID,
|
||||
Repository: "myapp",
|
||||
Digest: "sha256:abc123",
|
||||
HoldEndpoint: "did:web:hold.example.com",
|
||||
SchemaVersion: 2,
|
||||
MediaType: "application/vnd.oci.image.manifest.v1+json",
|
||||
ConfigDigest: "sha256:config123",
|
||||
ConfigSize: 1024,
|
||||
CreatedAt: time.Now(),
|
||||
Title: "My App",
|
||||
Description: "A cool application",
|
||||
SourceURL: "https://github.com/user/myapp",
|
||||
DocumentationURL: "https://docs.example.com",
|
||||
Licenses: "MIT",
|
||||
IconURL: "https://example.com/icon.png",
|
||||
ReadmeURL: "https://github.com/user/myapp/blob/main/README.md",
|
||||
}
|
||||
|
||||
id1, err := InsertManifest(db, manifest1)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to insert manifest: %v", err)
|
||||
}
|
||||
if id1 == 0 {
|
||||
t.Error("Expected non-zero manifest ID")
|
||||
}
|
||||
|
||||
// Verify the manifest was inserted correctly
|
||||
retrieved, err := GetManifest(db, manifest1.Digest)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to retrieve manifest: %v", err)
|
||||
}
|
||||
if retrieved.ID != id1 {
|
||||
t.Errorf("Expected ID %d, got %d", id1, retrieved.ID)
|
||||
}
|
||||
if retrieved.Title != "My App" {
|
||||
t.Errorf("Expected title 'My App', got '%s'", retrieved.Title)
|
||||
}
|
||||
if retrieved.ReadmeURL != "https://github.com/user/myapp/blob/main/README.md" {
|
||||
t.Errorf("Expected readme_url, got '%s'", retrieved.ReadmeURL)
|
||||
}
|
||||
|
||||
// Test 2: Insert manifest with minimal fields (NULLs for annotations)
|
||||
manifest2 := &Manifest{
|
||||
DID: testUser.DID,
|
||||
Repository: "minimal",
|
||||
Digest: "sha256:minimal123",
|
||||
HoldEndpoint: "did:web:hold.example.com",
|
||||
SchemaVersion: 2,
|
||||
MediaType: "application/vnd.oci.image.manifest.v1+json",
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
id2, err := InsertManifest(db, manifest2)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to insert minimal manifest: %v", err)
|
||||
}
|
||||
if id2 == 0 {
|
||||
t.Error("Expected non-zero manifest ID for minimal manifest")
|
||||
}
|
||||
|
||||
retrieved2, err := GetManifest(db, manifest2.Digest)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to retrieve minimal manifest: %v", err)
|
||||
}
|
||||
if retrieved2.Title != "" {
|
||||
t.Errorf("Expected empty title for minimal manifest, got '%s'", retrieved2.Title)
|
||||
}
|
||||
|
||||
// Test 3: Upsert existing manifest (same DID+repo+digest) - verify UPDATE path
|
||||
manifest1Updated := &Manifest{
|
||||
DID: testUser.DID,
|
||||
Repository: "myapp",
|
||||
Digest: "sha256:abc123", // Same digest - should trigger UPDATE
|
||||
HoldEndpoint: "did:web:hold2.example.com",
|
||||
SchemaVersion: 2,
|
||||
MediaType: "application/vnd.oci.image.manifest.v1+json",
|
||||
ConfigDigest: "sha256:newconfig",
|
||||
ConfigSize: 2048,
|
||||
CreatedAt: time.Now(),
|
||||
Title: "My App v2",
|
||||
Description: "An updated application",
|
||||
SourceURL: "https://github.com/user/myapp-v2",
|
||||
DocumentationURL: "https://v2.docs.example.com",
|
||||
Licenses: "Apache-2.0",
|
||||
IconURL: "https://example.com/icon-v2.png",
|
||||
ReadmeURL: "https://github.com/user/myapp/blob/v2/README.md",
|
||||
}
|
||||
|
||||
id3, err := InsertManifest(db, manifest1Updated)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to upsert manifest: %v", err)
|
||||
}
|
||||
// ID should be the same as the original insert (UPDATE, not INSERT)
|
||||
if id3 != id1 {
|
||||
t.Errorf("Expected upsert to return same ID %d, got %d", id1, id3)
|
||||
}
|
||||
|
||||
// Verify the manifest was updated
|
||||
retrievedUpdated, err := GetManifest(db, manifest1.Digest)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to retrieve updated manifest: %v", err)
|
||||
}
|
||||
if retrievedUpdated.Title != "My App v2" {
|
||||
t.Errorf("Expected updated title 'My App v2', got '%s'", retrievedUpdated.Title)
|
||||
}
|
||||
if retrievedUpdated.HoldEndpoint != "did:web:hold2.example.com" {
|
||||
t.Errorf("Expected updated hold_endpoint, got '%s'", retrievedUpdated.HoldEndpoint)
|
||||
}
|
||||
if retrievedUpdated.ReadmeURL != "https://github.com/user/myapp/blob/v2/README.md" {
|
||||
t.Errorf("Expected updated readme_url, got '%s'", retrievedUpdated.ReadmeURL)
|
||||
}
|
||||
|
||||
// Test 4: Verify count - should have 2 manifests (not 3, because one was upserted)
|
||||
digests, err := GetManifestDigestsForDID(db, testUser.DID)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get manifest digests: %v", err)
|
||||
}
|
||||
if len(digests) != 2 {
|
||||
t.Errorf("Expected 2 manifests after upsert, got %d", len(digests))
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserManagement(t *testing.T) {
|
||||
// Create in-memory test database
|
||||
db, err := InitDB(":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to init database: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
// Test 1: Upsert new user
|
||||
user1 := &User{
|
||||
DID: "did:plc:alice123",
|
||||
Handle: "alice.bsky.social",
|
||||
PDSEndpoint: "https://bsky.social",
|
||||
Avatar: "https://example.com/avatar.jpg",
|
||||
LastSeen: time.Now(),
|
||||
}
|
||||
|
||||
err = UpsertUser(db, user1)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to upsert new user: %v", err)
|
||||
}
|
||||
|
||||
// Test 2: GetUserByDID - found
|
||||
retrieved, err := GetUserByDID(db, user1.DID)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get user by DID: %v", err)
|
||||
}
|
||||
if retrieved == nil {
|
||||
t.Fatal("Expected user to be found, got nil")
|
||||
}
|
||||
if retrieved.Handle != "alice.bsky.social" {
|
||||
t.Errorf("Expected handle 'alice.bsky.social', got '%s'", retrieved.Handle)
|
||||
}
|
||||
if retrieved.Avatar != "https://example.com/avatar.jpg" {
|
||||
t.Errorf("Expected avatar URL, got '%s'", retrieved.Avatar)
|
||||
}
|
||||
|
||||
// Test 3: GetUserByHandle - found
|
||||
retrievedByHandle, err := GetUserByHandle(db, user1.Handle)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get user by handle: %v", err)
|
||||
}
|
||||
if retrievedByHandle == nil {
|
||||
t.Fatal("Expected user to be found by handle, got nil")
|
||||
}
|
||||
if retrievedByHandle.DID != user1.DID {
|
||||
t.Errorf("Expected DID '%s', got '%s'", user1.DID, retrievedByHandle.DID)
|
||||
}
|
||||
|
||||
// Test 4: GetUserByDID - not found
|
||||
notFound, err := GetUserByDID(db, "did:plc:nonexistent")
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error for nonexistent user, got: %v", err)
|
||||
}
|
||||
if notFound != nil {
|
||||
t.Error("Expected nil for nonexistent user")
|
||||
}
|
||||
|
||||
// Test 5: GetUserByHandle - not found
|
||||
notFoundByHandle, err := GetUserByHandle(db, "nonexistent.bsky.social")
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error for nonexistent handle, got: %v", err)
|
||||
}
|
||||
if notFoundByHandle != nil {
|
||||
t.Error("Expected nil for nonexistent handle")
|
||||
}
|
||||
|
||||
// Test 6: Upsert existing user (update)
|
||||
user1.Handle = "alice-new.bsky.social" // Change handle
|
||||
user1.Avatar = "" // Remove avatar
|
||||
user1.LastSeen = time.Now().Add(1 * time.Hour)
|
||||
|
||||
err = UpsertUser(db, user1)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to upsert existing user: %v", err)
|
||||
}
|
||||
|
||||
// Verify update
|
||||
updated, err := GetUserByDID(db, user1.DID)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get updated user: %v", err)
|
||||
}
|
||||
if updated.Handle != "alice-new.bsky.social" {
|
||||
t.Errorf("Expected updated handle 'alice-new.bsky.social', got '%s'", updated.Handle)
|
||||
}
|
||||
if updated.Avatar != "" {
|
||||
t.Errorf("Expected empty avatar after update, got '%s'", updated.Avatar)
|
||||
}
|
||||
|
||||
// Test 7: User with empty avatar (NULL)
|
||||
user2 := &User{
|
||||
DID: "did:plc:bob456",
|
||||
Handle: "bob.bsky.social",
|
||||
PDSEndpoint: "https://bsky.social",
|
||||
Avatar: "", // Empty avatar
|
||||
LastSeen: time.Now(),
|
||||
}
|
||||
|
||||
err = UpsertUser(db, user2)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to upsert user with empty avatar: %v", err)
|
||||
}
|
||||
|
||||
retrieved2, err := GetUserByDID(db, user2.DID)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get user with empty avatar: %v", err)
|
||||
}
|
||||
if retrieved2.Avatar != "" {
|
||||
t.Errorf("Expected empty avatar, got '%s'", retrieved2.Avatar)
|
||||
}
|
||||
}
|
||||
|
||||
func TestManifestOperations(t *testing.T) {
|
||||
// Create in-memory test database
|
||||
db, err := InitDB(":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to init database: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
// Setup: Create test user
|
||||
testUser := &User{
|
||||
DID: "did:plc:test123",
|
||||
Handle: "test.bsky.social",
|
||||
PDSEndpoint: "https://test.pds.example.com",
|
||||
LastSeen: time.Now(),
|
||||
}
|
||||
if err := UpsertUser(db, testUser); err != nil {
|
||||
t.Fatalf("Failed to create test user: %v", err)
|
||||
}
|
||||
|
||||
// Insert test manifests
|
||||
manifests := []*Manifest{
|
||||
{
|
||||
DID: testUser.DID,
|
||||
Repository: "app1",
|
||||
Digest: "sha256:aaa",
|
||||
HoldEndpoint: "did:web:hold.example.com",
|
||||
SchemaVersion: 2,
|
||||
MediaType: "application/vnd.oci.image.manifest.v1+json",
|
||||
CreatedAt: time.Now(),
|
||||
Title: "App 1",
|
||||
},
|
||||
{
|
||||
DID: testUser.DID,
|
||||
Repository: "app1",
|
||||
Digest: "sha256:bbb",
|
||||
HoldEndpoint: "did:web:hold.example.com",
|
||||
SchemaVersion: 2,
|
||||
MediaType: "application/vnd.oci.image.manifest.v1+json",
|
||||
CreatedAt: time.Now(),
|
||||
Title: "App 1 v2",
|
||||
},
|
||||
{
|
||||
DID: testUser.DID,
|
||||
Repository: "app2",
|
||||
Digest: "sha256:ccc",
|
||||
HoldEndpoint: "did:web:hold.example.com",
|
||||
SchemaVersion: 2,
|
||||
MediaType: "application/vnd.oci.image.manifest.v1+json",
|
||||
CreatedAt: time.Now(),
|
||||
Title: "App 2",
|
||||
},
|
||||
}
|
||||
|
||||
for _, m := range manifests {
|
||||
_, err := InsertManifest(db, m)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to insert manifest: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Test 1: GetManifest - found
|
||||
retrieved, err := GetManifest(db, "sha256:aaa")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get manifest: %v", err)
|
||||
}
|
||||
if retrieved.Title != "App 1" {
|
||||
t.Errorf("Expected title 'App 1', got '%s'", retrieved.Title)
|
||||
}
|
||||
|
||||
// Test 2: GetManifest - not found
|
||||
notFound, err := GetManifest(db, "sha256:nonexistent")
|
||||
if err == nil {
|
||||
t.Error("Expected error for nonexistent manifest")
|
||||
}
|
||||
if notFound != nil {
|
||||
t.Error("Expected nil for nonexistent manifest")
|
||||
}
|
||||
|
||||
// Test 3: GetManifestDigestsForDID - multiple manifests
|
||||
digests, err := GetManifestDigestsForDID(db, testUser.DID)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get manifest digests: %v", err)
|
||||
}
|
||||
if len(digests) != 3 {
|
||||
t.Errorf("Expected 3 manifests, got %d", len(digests))
|
||||
}
|
||||
|
||||
// Test 4: GetManifestDigestsForDID - no manifests
|
||||
noDigests, err := GetManifestDigestsForDID(db, "did:plc:nonexistent")
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error for user with no manifests, got: %v", err)
|
||||
}
|
||||
if len(noDigests) != 0 {
|
||||
t.Errorf("Expected 0 manifests for nonexistent user, got %d", len(noDigests))
|
||||
}
|
||||
|
||||
// Test 5: DeleteManifestsNotInList - keep some, delete others
|
||||
keepDigests := []string{"sha256:aaa", "sha256:ccc"}
|
||||
err = DeleteManifestsNotInList(db, testUser.DID, keepDigests)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to delete manifests not in list: %v", err)
|
||||
}
|
||||
|
||||
// Verify only sha256:aaa and sha256:ccc remain
|
||||
remaining, err := GetManifestDigestsForDID(db, testUser.DID)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get remaining manifests: %v", err)
|
||||
}
|
||||
if len(remaining) != 2 {
|
||||
t.Errorf("Expected 2 remaining manifests, got %d", len(remaining))
|
||||
}
|
||||
|
||||
// Verify sha256:bbb was deleted
|
||||
deleted, err := GetManifest(db, "sha256:bbb")
|
||||
if err == nil {
|
||||
t.Error("Expected error for deleted manifest")
|
||||
}
|
||||
if deleted != nil {
|
||||
t.Error("Expected nil for deleted manifest")
|
||||
}
|
||||
|
||||
// Test 6: DeleteManifestsNotInList - empty list (delete all)
|
||||
err = DeleteManifestsNotInList(db, testUser.DID, []string{})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to delete all manifests: %v", err)
|
||||
}
|
||||
|
||||
allGone, err := GetManifestDigestsForDID(db, testUser.DID)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get manifests after delete all: %v", err)
|
||||
}
|
||||
if len(allGone) != 0 {
|
||||
t.Errorf("Expected 0 manifests after delete all, got %d", len(allGone))
|
||||
}
|
||||
|
||||
// Test 7: DeleteManifest - specific deletion (re-insert for this test)
|
||||
manifest := &Manifest{
|
||||
DID: testUser.DID,
|
||||
Repository: "app3",
|
||||
Digest: "sha256:ddd",
|
||||
HoldEndpoint: "did:web:hold.example.com",
|
||||
SchemaVersion: 2,
|
||||
MediaType: "application/vnd.oci.image.manifest.v1+json",
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
_, err = InsertManifest(db, manifest)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to insert manifest for delete test: %v", err)
|
||||
}
|
||||
|
||||
// Delete by DID+repo+digest
|
||||
err = DeleteManifest(db, testUser.DID, "app3", "sha256:ddd")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to delete manifest: %v", err)
|
||||
}
|
||||
|
||||
// Verify deletion
|
||||
afterDelete, err := GetManifest(db, "sha256:ddd")
|
||||
if err == nil {
|
||||
t.Error("Expected error for deleted manifest")
|
||||
}
|
||||
if afterDelete != nil {
|
||||
t.Error("Expected nil after deletion")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsManifestTagged(t *testing.T) {
|
||||
// Create in-memory test database
|
||||
db, err := InitDB(":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to init database: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
// Setup: Create test user
|
||||
testUser := &User{
|
||||
DID: "did:plc:test123",
|
||||
Handle: "test.bsky.social",
|
||||
PDSEndpoint: "https://test.pds.example.com",
|
||||
LastSeen: time.Now(),
|
||||
}
|
||||
if err := UpsertUser(db, testUser); err != nil {
|
||||
t.Fatalf("Failed to create test user: %v", err)
|
||||
}
|
||||
|
||||
// Insert manifest
|
||||
manifest := &Manifest{
|
||||
DID: testUser.DID,
|
||||
Repository: "myapp",
|
||||
Digest: "sha256:abc123",
|
||||
HoldEndpoint: "did:web:hold.example.com",
|
||||
SchemaVersion: 2,
|
||||
MediaType: "application/vnd.oci.image.manifest.v1+json",
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
_, err = InsertManifest(db, manifest)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to insert manifest: %v", err)
|
||||
}
|
||||
|
||||
// Test 1: Manifest without tags
|
||||
tagged, err := IsManifestTagged(db, testUser.DID, "myapp", "sha256:abc123")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to check if manifest is tagged: %v", err)
|
||||
}
|
||||
if tagged {
|
||||
t.Error("Expected manifest to not be tagged")
|
||||
}
|
||||
|
||||
// Test 2: Add a tag
|
||||
tag := &Tag{
|
||||
DID: testUser.DID,
|
||||
Repository: "myapp",
|
||||
Tag: "latest",
|
||||
Digest: "sha256:abc123",
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
err = UpsertTag(db, tag)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to insert tag: %v", err)
|
||||
}
|
||||
|
||||
// Test 3: Manifest with tag
|
||||
taggedNow, err := IsManifestTagged(db, testUser.DID, "myapp", "sha256:abc123")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to check if manifest is tagged: %v", err)
|
||||
}
|
||||
if !taggedNow {
|
||||
t.Error("Expected manifest to be tagged")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTagOperations(t *testing.T) {
|
||||
// Create in-memory test database
|
||||
db, err := InitDB(":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to init database: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
// Setup: Create test user and manifests
|
||||
testUser := &User{
|
||||
DID: "did:plc:test123",
|
||||
Handle: "test.bsky.social",
|
||||
PDSEndpoint: "https://test.pds.example.com",
|
||||
LastSeen: time.Now(),
|
||||
}
|
||||
if err := UpsertUser(db, testUser); err != nil {
|
||||
t.Fatalf("Failed to create test user: %v", err)
|
||||
}
|
||||
|
||||
manifest := &Manifest{
|
||||
DID: testUser.DID,
|
||||
Repository: "myapp",
|
||||
Digest: "sha256:abc123",
|
||||
HoldEndpoint: "did:web:hold.example.com",
|
||||
SchemaVersion: 2,
|
||||
MediaType: "application/vnd.oci.image.manifest.v1+json",
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
_, err = InsertManifest(db, manifest)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to insert manifest: %v", err)
|
||||
}
|
||||
|
||||
// Test 1: UpsertTag - insert new tag
|
||||
tag1 := &Tag{
|
||||
DID: testUser.DID,
|
||||
Repository: "myapp",
|
||||
Tag: "latest",
|
||||
Digest: "sha256:abc123",
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
err = UpsertTag(db, tag1)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to upsert tag: %v", err)
|
||||
}
|
||||
|
||||
// Test 2: GetTagsForDID - should have 1 tag
|
||||
tags, err := GetTagsForDID(db, testUser.DID)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get tags: %v", err)
|
||||
}
|
||||
if len(tags) != 1 {
|
||||
t.Errorf("Expected 1 tag, got %d", len(tags))
|
||||
}
|
||||
if tags[0].Repository != "myapp" || tags[0].Tag != "latest" {
|
||||
t.Errorf("Expected myapp:latest, got %s:%s", tags[0].Repository, tags[0].Tag)
|
||||
}
|
||||
|
||||
// Test 3: UpsertTag - update existing tag (point to new digest)
|
||||
tag1Updated := &Tag{
|
||||
DID: testUser.DID,
|
||||
Repository: "myapp",
|
||||
Tag: "latest", // Same tag
|
||||
Digest: "sha256:new456",
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
err = UpsertTag(db, tag1Updated)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to update tag: %v", err)
|
||||
}
|
||||
|
||||
// Verify update - should still have 1 tag but with new digest
|
||||
tagsAfterUpdate, err := GetTagsForDID(db, testUser.DID)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get tags after update: %v", err)
|
||||
}
|
||||
if len(tagsAfterUpdate) != 1 {
|
||||
t.Errorf("Expected 1 tag after update, got %d", len(tagsAfterUpdate))
|
||||
}
|
||||
if tagsAfterUpdate[0].Tag != "latest" {
|
||||
t.Errorf("Expected tag 'latest', got '%s'", tagsAfterUpdate[0].Tag)
|
||||
}
|
||||
|
||||
// Test 4: Add more tags
|
||||
tag2 := &Tag{
|
||||
DID: testUser.DID,
|
||||
Repository: "myapp",
|
||||
Tag: "v1.0.0",
|
||||
Digest: "sha256:abc123",
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
tag3 := &Tag{
|
||||
DID: testUser.DID,
|
||||
Repository: "otherapp",
|
||||
Tag: "latest",
|
||||
Digest: "sha256:xyz789",
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
err = UpsertTag(db, tag2)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to insert tag2: %v", err)
|
||||
}
|
||||
err = UpsertTag(db, tag3)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to insert tag3: %v", err)
|
||||
}
|
||||
|
||||
// Verify count
|
||||
allTags, err := GetTagsForDID(db, testUser.DID)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get all tags: %v", err)
|
||||
}
|
||||
if len(allTags) != 3 {
|
||||
t.Errorf("Expected 3 tags, got %d", len(allTags))
|
||||
}
|
||||
|
||||
// Test 5: DeleteTagsNotInList - keep some, delete others
|
||||
keepTags := []struct{ Repository, Tag string }{
|
||||
{Repository: "myapp", Tag: "latest"},
|
||||
{Repository: "otherapp", Tag: "latest"},
|
||||
}
|
||||
err = DeleteTagsNotInList(db, testUser.DID, keepTags)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to delete tags not in list: %v", err)
|
||||
}
|
||||
|
||||
// Verify v1.0.0 was deleted
|
||||
remaining, err := GetTagsForDID(db, testUser.DID)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get remaining tags: %v", err)
|
||||
}
|
||||
if len(remaining) != 2 {
|
||||
t.Errorf("Expected 2 remaining tags, got %d", len(remaining))
|
||||
}
|
||||
|
||||
// Test 6: DeleteTag - specific deletion
|
||||
err = DeleteTag(db, testUser.DID, "myapp", "latest")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to delete tag: %v", err)
|
||||
}
|
||||
|
||||
// Verify deletion
|
||||
afterDelete, err := GetTagsForDID(db, testUser.DID)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get tags after delete: %v", err)
|
||||
}
|
||||
if len(afterDelete) != 1 {
|
||||
t.Errorf("Expected 1 tag after delete, got %d", len(afterDelete))
|
||||
}
|
||||
if afterDelete[0].Repository != "otherapp" {
|
||||
t.Errorf("Wrong tag remained: %s:%s", afterDelete[0].Repository, afterDelete[0].Tag)
|
||||
}
|
||||
|
||||
// Test 7: GetTagsForDID - no tags
|
||||
noTags, err := GetTagsForDID(db, "did:plc:nonexistent")
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error for user with no tags, got: %v", err)
|
||||
}
|
||||
if len(noTags) != 0 {
|
||||
t.Errorf("Expected 0 tags for nonexistent user, got %d", len(noTags))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTagsWithPlatforms(t *testing.T) {
|
||||
// Create in-memory test database
|
||||
db, err := InitDB(":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to init database: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
// Setup: Create test user
|
||||
testUser := &User{
|
||||
DID: "did:plc:test123",
|
||||
Handle: "test.bsky.social",
|
||||
PDSEndpoint: "https://test.pds.example.com",
|
||||
LastSeen: time.Now(),
|
||||
}
|
||||
if err := UpsertUser(db, testUser); err != nil {
|
||||
t.Fatalf("Failed to create test user: %v", err)
|
||||
}
|
||||
|
||||
// Test 1: Single-arch manifest (no platform info)
|
||||
singleArchManifest := &Manifest{
|
||||
DID: testUser.DID,
|
||||
Repository: "myapp",
|
||||
Digest: "sha256:single",
|
||||
HoldEndpoint: "did:web:hold.example.com",
|
||||
SchemaVersion: 2,
|
||||
MediaType: "application/vnd.oci.image.manifest.v1+json",
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
manifestID1, err := InsertManifest(db, singleArchManifest)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to insert single-arch manifest: %v", err)
|
||||
}
|
||||
|
||||
singleTag := &Tag{
|
||||
DID: testUser.DID,
|
||||
Repository: "myapp",
|
||||
Tag: "latest",
|
||||
Digest: "sha256:single",
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
err = UpsertTag(db, singleTag)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to insert single-arch tag: %v", err)
|
||||
}
|
||||
|
||||
tagsWithPlatforms, err := GetTagsWithPlatforms(db, testUser.DID, "myapp")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get tags with platforms: %v", err)
|
||||
}
|
||||
if len(tagsWithPlatforms) != 1 {
|
||||
t.Fatalf("Expected 1 tag, got %d", len(tagsWithPlatforms))
|
||||
}
|
||||
if tagsWithPlatforms[0].IsMultiArch {
|
||||
t.Error("Expected single-arch tag to not be multi-arch")
|
||||
}
|
||||
if len(tagsWithPlatforms[0].Platforms) != 0 {
|
||||
t.Errorf("Expected 0 platforms for single-arch, got %d", len(tagsWithPlatforms[0].Platforms))
|
||||
}
|
||||
|
||||
// Test 2: Multi-arch manifest (manifest list with platform info)
|
||||
multiArchManifest := &Manifest{
|
||||
DID: testUser.DID,
|
||||
Repository: "multiapp",
|
||||
Digest: "sha256:multi",
|
||||
HoldEndpoint: "did:web:hold.example.com",
|
||||
SchemaVersion: 2,
|
||||
MediaType: "application/vnd.oci.image.index.v1+json", // Manifest list
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
manifestID2, err := InsertManifest(db, multiArchManifest)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to insert multi-arch manifest: %v", err)
|
||||
}
|
||||
|
||||
// Add manifest references with platform info
|
||||
ref1 := &ManifestReference{
|
||||
ManifestID: manifestID2,
|
||||
Digest: "sha256:amd64",
|
||||
Size: 1000,
|
||||
MediaType: "application/vnd.oci.image.manifest.v1+json",
|
||||
PlatformOS: "linux",
|
||||
PlatformArchitecture: "amd64",
|
||||
ReferenceIndex: 0,
|
||||
}
|
||||
ref2 := &ManifestReference{
|
||||
ManifestID: manifestID2,
|
||||
Digest: "sha256:arm64",
|
||||
Size: 1000,
|
||||
MediaType: "application/vnd.oci.image.manifest.v1+json",
|
||||
PlatformOS: "linux",
|
||||
PlatformArchitecture: "arm64",
|
||||
ReferenceIndex: 1,
|
||||
}
|
||||
err = InsertManifestReference(db, ref1)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to insert manifest reference 1: %v", err)
|
||||
}
|
||||
err = InsertManifestReference(db, ref2)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to insert manifest reference 2: %v", err)
|
||||
}
|
||||
|
||||
multiTag := &Tag{
|
||||
DID: testUser.DID,
|
||||
Repository: "multiapp",
|
||||
Tag: "latest",
|
||||
Digest: "sha256:multi",
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
err = UpsertTag(db, multiTag)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to insert multi-arch tag: %v", err)
|
||||
}
|
||||
|
||||
multiTagsWithPlatforms, err := GetTagsWithPlatforms(db, testUser.DID, "multiapp")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get multi-arch tags with platforms: %v", err)
|
||||
}
|
||||
if len(multiTagsWithPlatforms) != 1 {
|
||||
t.Fatalf("Expected 1 tag, got %d", len(multiTagsWithPlatforms))
|
||||
}
|
||||
if !multiTagsWithPlatforms[0].IsMultiArch {
|
||||
t.Error("Expected multi-arch tag to be marked as multi-arch")
|
||||
}
|
||||
if len(multiTagsWithPlatforms[0].Platforms) != 2 {
|
||||
t.Errorf("Expected 2 platforms for multi-arch, got %d", len(multiTagsWithPlatforms[0].Platforms))
|
||||
}
|
||||
|
||||
// Verify platform details
|
||||
platforms := multiTagsWithPlatforms[0].Platforms
|
||||
if platforms[0].OS != "linux" || platforms[0].Architecture != "amd64" {
|
||||
t.Errorf("Expected linux/amd64, got %s/%s", platforms[0].OS, platforms[0].Architecture)
|
||||
}
|
||||
if platforms[1].OS != "linux" || platforms[1].Architecture != "arm64" {
|
||||
t.Errorf("Expected linux/arm64, got %s/%s", platforms[1].OS, platforms[1].Architecture)
|
||||
}
|
||||
|
||||
// Don't use manifestID1 since it's not accessed after assignment
|
||||
_ = manifestID1
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user