package model import ( "testing" "time" "github.com/stretchr/testify/assert" "github.com/shcizo/package-updater/internal/protocol" ) func TestLocationKey(t *testing.T) { cases := []struct { name, agentKey, want string }{ {"compose is per agent", "compose|slusk|web", "compose|7|slusk|web"}, {"container is per agent", "container|adhoc", "container|7|adhoc"}, {"swarm is per cluster", "swarm|gitea_gitea", "swarm|cluster-1|gitea_gitea"}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { assert.Equal(t, c.want, LocationKey(7, "cluster-1", c.agentKey)) }) } } func TestDisplayName(t *testing.T) { cases := []struct { name string svc Service want string }{ {"compose", Service{Kind: protocol.KindCompose, ComposeProject: "slusk", ComposeService: "web"}, "slusk/web"}, {"swarm", Service{Kind: protocol.KindSwarm, SwarmService: "gitea_gitea"}, "gitea_gitea"}, {"container", Service{Kind: protocol.KindContainer, ContainerName: "adhoc"}, "adhoc"}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { assert.Equal(t, c.want, c.svc.DisplayName()) }) } } func TestImageVersionRef(t *testing.T) { cases := []struct { name string v ImageVersion want string }{ {"tag and digest", ImageVersion{Repo: "r/x", Tag: "1", Digest: "sha256:a"}, "r/x:1@sha256:a"}, {"tag only", ImageVersion{Repo: "r/x", Tag: "1"}, "r/x:1"}, {"digest only", ImageVersion{Repo: "r/x", Digest: "sha256:a"}, "r/x@sha256:a"}, {"registry port", ImageVersion{Repo: "localhost:5000/x", Tag: "1"}, "localhost:5000/x:1"}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { assert.Equal(t, c.want, c.v.Ref()) }) } } const ( dA = "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" dB = "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ) func TestRunsRef(t *testing.T) { cases := []struct { name string desired, actualRef, actualDig string want bool }{ {"same string", "repo:v1@" + dA, "repo:v1@" + dA, "", true}, {"tag and digest, actual reports it separately", "repo:tag@" + dA, "repo:tag", dA, true}, {"bare image reports latest digest", "postgres:latest@" + dA, "postgres", dA, true}, {"tagless actual with the same digest", "repo:tag@" + dA, "repo@" + dA, "", true}, {"same digest under another tag", "repo:v1@" + dA, "repo:v2@" + dA, "", true}, {"same digest, actual tag differs, digest reported", "repo:v1@" + dA, "repo:v2", dA, true}, {"same tag, actual digest unknown", "repo:tag@" + dA, "repo:tag", "", true}, {"desired without digest, same tag", "repo:tag", "repo:tag@" + dA, "", true}, {"bare desired, latest actual", "repo", "repo:latest", "", true}, {"registry with port", "localhost:5000/foo:v1@" + dA, "localhost:5000/foo:v1", dA, true}, {"registry with port, other digest", "localhost:5000/foo:v1@" + dA, "localhost:5000/foo:v1", dB, false}, {"same tag, other digest", "repo:tag@" + dA, "repo:tag", dB, false}, {"other digest in the ref", "repo:tag@" + dA, "repo:tag@" + dB, "", false}, {"other tag, digest unknown", "repo:v1@" + dA, "repo:v2", "", false}, {"tagless desired, tagged actual, digest unknown", "repo@" + dA, "repo:v1", "", false}, {"other repo", "repo:tag@" + dA, "other:tag", dA, false}, {"no docker.io normalisation", "nginx:1@" + dA, "docker.io/library/nginx:1", dA, false}, {"case-sensitive repo", "Repo:1@" + dA, "repo:1", dA, false}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { assert.Equal(t, c.want, RunsRef(c.desired, c.actualRef, c.actualDig)) }) } } func TestDrifted(t *testing.T) { now := time.Now() drifted := Service{DesiredRef: "repo:tag@" + dA, ActualRef: "repo:tag", ActualDigest: dB} cases := []struct { name string mutate func(*Service) want bool }{ {"runs another digest", func(*Service) {}, true}, {"never updated", func(s *Service) { s.DesiredRef = "" }, false}, {"removed", func(s *Service) { s.RemovedAt = &now }, false}, {"awaiting inventory", func(s *Service) { s.AwaitingInventory = true }, false}, {"runs the desired digest", func(s *Service) { s.ActualDigest = dA }, false}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { svc := drifted c.mutate(&svc) assert.Equal(t, c.want, svc.Drifted()) }) } } func TestRunningRef(t *testing.T) { cases := []struct { name, actualRef, actualDigest, want string }{ {"ref with digest is kept", "repo:v1@" + dB, dA, "repo:v1@" + dB}, {"tag plus reported digest", "repo:v1", dA, "repo:v1@" + dA}, {"bare image becomes latest", "postgres", dA, "postgres:latest@" + dA}, {"registry with port", "localhost:5000/foo:v1", dA, "localhost:5000/foo:v1@" + dA}, {"no digest known", "repo:v1", "", "repo:v1"}, {"malformed digest", "repo:v1", "sha256:abc", "repo:v1"}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { assert.Equal(t, c.want, RunningRef(c.actualRef, c.actualDigest)) }) } } func TestJob_Active(t *testing.T) { cases := []struct { status JobStatus want bool }{ {JobQueued, true}, {JobRunning, true}, {JobSucceeded, false}, {JobFailed, false}, } for _, c := range cases { t.Run(string(c.status), func(t *testing.T) { assert.Equal(t, c.want, Job{Status: c.status}.Active()) }) } }