From 687e411052d15bfd0334b5018f9b6e5ec6d1ae93 Mon Sep 17 00:00:00 2001 From: Samuel Enocsson Date: Tue, 13 Oct 2026 08:15:29 +0200 Subject: [PATCH] test(e2e): pick a pinned version and poll an auto service end to end (#9) --- internal/e2e/versions_test.go | 93 +++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 internal/e2e/versions_test.go diff --git a/internal/e2e/versions_test.go b/internal/e2e/versions_test.go new file mode 100644 index 0000000..a48bb9b --- /dev/null +++ b/internal/e2e/versions_test.go @@ -0,0 +1,93 @@ +package e2e + +import ( + "context" + "io" + "log/slog" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/shcizo/package-updater/internal/master/model" + "github.com/shcizo/package-updater/internal/master/poller" + "github.com/shcizo/package-updater/internal/protocol" +) + +// jobCount is the number of jobs created so far. +func (m *memStore) jobCount() int { + m.mu.Lock() + defer m.mu.Unlock() + return len(m.jobs) +} + +// fixedRegistry answers every lookup from a fixed table. +type fixedRegistry struct { + digests map[string]string + calls int +} + +func (f *fixedRegistry) Digest(_ context.Context, ref string) (string, error) { + f.calls++ + return f.digests[ref], nil +} + +func TestPickPinnedVersion(t *testing.T) { + env := newJobEnv(t) + env.store.seedVersion(model.ImageVersion{ID: 5, Repo: slusk, Tag: "1.3.0", Digest: "sha256:old130", Source: model.SourceRunning}) + + job, err := env.jobs.TriggerVersion(context.Background(), 1, 5, "alice") + + require.NoError(t, err) + assert.Equal(t, slusk+":1.3.0@sha256:old130", job.ToRef) + select { + case ref := <-env.exec.started: + assert.Equal(t, slusk+":1.3.0@sha256:old130", ref, "the agent receives exactly the picked version") + case <-time.After(waitFor): + t.Fatal("the agent never received the job") + } + close(env.exec.release) + done := env.waitStatus(t, job.ID, model.JobSucceeded) + assert.Equal(t, model.TriggerManual, done.Trigger) + assert.Equal(t, "alice", done.RequestedBy) +} + +func TestPickVersionOfAnotherRepoIsRefused(t *testing.T) { + env := newJobEnv(t) + env.store.seedVersion(model.ImageVersion{ID: 6, Repo: "evil/other", Tag: "1", Digest: "sha256:x", Source: model.SourcePoll}) + + _, err := env.jobs.TriggerVersion(context.Background(), 1, 6, "alice") + + assert.ErrorIs(t, err, model.ErrNotFound) + assert.Zero(t, env.store.jobCount(), "no job was created") +} + +func TestPollStartsAutoJob(t *testing.T) { + env := newJobEnv(t) + env.store.seedService(model.Service{ID: 1, Kind: protocol.KindCompose, ComposeProject: "slusk", ComposeService: "web", + AgentID: ptr(agentID), ImageRepo: slusk, ActualRef: slusk + ":1.4.2@sha256:old", ActualDigest: "sha256:old", + UpdateMode: model.ModeAuto}) + reg := &fixedRegistry{digests: map[string]string{slusk + ":1.4.2": "sha256:new"}} + p := poller.New(env.store, env.jobs, reg, env.m.broker, poller.Config{Workers: 1}, slog.New(slog.NewTextHandler(io.Discard, nil))) + + p.Cycle(context.Background()) + + assert.Equal(t, slusk+":1.4.2", <-env.exec.started, "the agent gets repo:tag and resolves the digest itself") + require.Equal(t, 1, env.store.jobCount()) + job := env.job(t, 1) + assert.Equal(t, model.TriggerPoll, job.Trigger) + assert.Equal(t, slusk+":1.4.2", job.ToRef) + svc, err := env.store.GetService(context.Background(), 1) + require.NoError(t, err) + assert.Equal(t, slusk+":1.4.2", svc.AvailableRef) + assert.Equal(t, "sha256:new", svc.AvailableDigest) + assert.Equal(t, model.SourcePoll, svc.AvailableSource) + + p.Cycle(context.Background()) + + assert.Equal(t, 2, reg.calls, "the registry is asked again") + assert.Equal(t, 1, env.store.jobCount(), "the same digest does not start another job") + close(env.exec.release) + env.waitStatus(t, job.ID, model.JobSucceeded) +}