Skip to content

Commit d8c8f68

Browse files
authored
Merge pull request #370 from docker/test-tag
Added integration tests for tag command
2 parents 1ed8d80 + 6765de5 commit d8c8f68

File tree

1 file changed

+165
-0
lines changed

1 file changed

+165
-0
lines changed

cmd/cli/commands/integration_test.go

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,3 +462,168 @@ func TestIntegration_InspectModel(t *testing.T) {
462462
require.NoError(t, err)
463463
require.Empty(t, strings.TrimSpace(models), "Model should be removed")
464464
}
465+
466+
// TestIntegration_TagModel tests tagging a model with various source and target reference formats
467+
// to ensure proper reference normalization and tag creation.
468+
func TestIntegration_TagModel(t *testing.T) {
469+
env := setupTestEnv(t)
470+
471+
// Ensure no models exist initially
472+
models, err := listModels(false, env.client, true, false, "")
473+
require.NoError(t, err)
474+
if len(models) != 0 {
475+
t.Fatal("Expected no initial models, but found some")
476+
}
477+
478+
// Create and push a test model with default org (ai/tag-test:latest)
479+
modelRef := "ai/tag-test:latest"
480+
modelID, hostFQDN, networkFQDN, digest := createAndPushTestModel(t, env.registryURL, modelRef, 2048)
481+
t.Logf("Test model pushed: %s (ID: %s) FQDN: %s Digest: %s", hostFQDN, modelID, networkFQDN, digest)
482+
483+
// Pull the model using a simple reference
484+
pullRef := "tag-test"
485+
t.Logf("Pulling model with reference: %s", pullRef)
486+
err = pullModel(newPullCmd(), env.client, pullRef, true)
487+
require.NoError(t, err, "Failed to pull model")
488+
489+
// Verify the model was pulled
490+
models, err = listModels(false, env.client, true, false, "")
491+
require.NoError(t, err)
492+
truncatedID := modelID[7:19]
493+
require.Equal(t, truncatedID, strings.TrimSpace(models), "Model not found after pull")
494+
495+
// Generate all possible source references using the unified system
496+
sourceInfo := modelInfo{
497+
name: "tag-test",
498+
org: "ai",
499+
tag: "latest",
500+
registry: "registry.local:5000",
501+
modelID: modelID,
502+
digest: digest,
503+
expectedName: "ai/tag-test:v1",
504+
}
505+
sourceRefs := generateReferenceTestCases(sourceInfo)
506+
507+
// Define target reference formats to test (including explicit registry references)
508+
targetFormats := []struct {
509+
name string
510+
target string
511+
}{
512+
{name: "simple name", target: "tag-test"},
513+
{name: "simple name with tag", target: "target-tag-test:v2"},
514+
{name: "with org", target: "ai/target-tag-test:latest"},
515+
{name: "different org", target: "test/target-tag-test:v1"},
516+
{name: "custom org", target: "custom/target-tag-test:cross-org"},
517+
{name: "explicit registry with org", target: "registry.local:5000/ai/target-tag-test:explicit"},
518+
{name: "explicit registry different org", target: "registry.local:5000/target-test/tag-test:fqdn"},
519+
{name: "explicit registry custom org", target: "registry.local:5000/custom/target-tag-test:with-reg"},
520+
{name: "explicit custom registry custom org", target: "other-registry.local:5000/custom/target-tag-test:with-reg"},
521+
}
522+
523+
// Build test cases by combining source references with target formats
524+
type tagTestCase struct {
525+
name string
526+
sourceRef string
527+
targetRef string
528+
}
529+
530+
var testCases []tagTestCase
531+
532+
// Test all combinations of source references and target formats
533+
for _, srcCase := range sourceRefs {
534+
535+
if strings.Contains(srcCase.name, "model ID") {
536+
// Skip ID-based references for tagging tests
537+
// TODO : Support tagging by ID in the future
538+
continue
539+
}
540+
541+
// Nested loop - test this source with ALL targets
542+
for _, targetFormat := range targetFormats {
543+
testCases = append(testCases, tagTestCase{
544+
name: fmt.Sprintf("source: %s -> target: %s", srcCase.name, targetFormat.name),
545+
sourceRef: srcCase.ref,
546+
targetRef: targetFormat.target,
547+
})
548+
}
549+
}
550+
551+
// Track all created tags for verification
552+
createdTags := make(map[string]bool)
553+
554+
for _, tc := range testCases {
555+
t.Run(tc.name, func(t *testing.T) {
556+
t.Logf("Tagging %s as %s", tc.sourceRef, tc.targetRef)
557+
558+
// Perform the tag operation
559+
err := tagModel(newTagCmd(), env.client, tc.sourceRef, tc.targetRef)
560+
require.NoError(t, err, "Failed to tag model with source=%s target=%s", tc.sourceRef, tc.targetRef)
561+
562+
// Track this tag
563+
createdTags[tc.targetRef] = true
564+
565+
// Verify the new tag exists and points to the correct model
566+
taggedModel, err := env.client.Inspect(tc.targetRef, false)
567+
require.NoError(t, err, "Failed to inspect newly tagged model with reference: %s", tc.targetRef)
568+
569+
// Verify the model ID matches the original
570+
require.Equal(t, modelID, taggedModel.ID,
571+
"Tagged model ID mismatch. Expected: %s, Got: %s", modelID, taggedModel.ID)
572+
573+
// Verify the digest matches
574+
require.Equal(t, digest, taggedModel.ID,
575+
"Tagged model digest mismatch. Expected: %s, Got: %s", digest, taggedModel.ID)
576+
577+
t.Logf("✓ Successfully created tag %s pointing to model %s", tc.targetRef, truncatedID)
578+
579+
// Verify the original source tag/reference still exists
580+
originalModel, err := env.client.Inspect(tc.sourceRef, false)
581+
require.NoError(t, err, "Original source reference should still be valid: %s", tc.sourceRef)
582+
require.Equal(t, modelID, originalModel.ID, "Original source should point to same model")
583+
t.Logf("✓ Verified original source %s still exists", tc.sourceRef)
584+
})
585+
}
586+
587+
// Final verification: List the model and verify all tags are present
588+
t.Run("verify all tags in model inspect", func(t *testing.T) {
589+
inspectedModel, err := env.client.Inspect(modelID, false)
590+
require.NoError(t, err, "Failed to inspect model by ID")
591+
592+
t.Logf("Model has %d tags: %v", len(inspectedModel.Tags), inspectedModel.Tags)
593+
594+
// The model should have at least the original tag plus all created tags
595+
require.GreaterOrEqual(t, len(inspectedModel.Tags), len(createdTags)+1,
596+
"Model should have at least %d tags (original + created)", len(createdTags)+1)
597+
598+
// Verify each created tag is in the model's tag list
599+
for expectedTag := range createdTags {
600+
found := false
601+
for _, actualTag := range inspectedModel.Tags {
602+
if actualTag == expectedTag || actualTag == fmt.Sprintf("%s:latest", expectedTag) { // Handle implicit latest tag
603+
found = true
604+
break
605+
}
606+
}
607+
require.True(t, found, "Expected tag %s not found in model's tag list", expectedTag)
608+
}
609+
610+
t.Logf("✓ All %d created tags verified in model's tag list", len(createdTags))
611+
})
612+
613+
// Test error case: tagging non-existent model
614+
t.Run("error on non-existent model", func(t *testing.T) {
615+
err := tagModel(newTagCmd(), env.client, "non-existent-model:v1", "ai/should-fail:latest")
616+
require.Error(t, err, "Should fail when tagging non-existent model")
617+
t.Logf("✓ Correctly failed to tag non-existent model: %v", err)
618+
})
619+
620+
// Cleanup: remove the model
621+
t.Logf("Removing model %s", truncatedID)
622+
err = removeModel(env.client, modelID)
623+
require.NoError(t, err, "Failed to remove model")
624+
625+
// Verify model was removed
626+
models, err = listModels(false, env.client, true, false, "")
627+
require.NoError(t, err)
628+
require.Empty(t, strings.TrimSpace(models), "Model should be removed")
629+
}

0 commit comments

Comments
 (0)