diff --git a/internal/api/handlers.go b/internal/api/handlers.go index e9af556..420ff35 100644 --- a/internal/api/handlers.go +++ b/internal/api/handlers.go @@ -56,7 +56,12 @@ func (h *Handlers) Update(w http.ResponseWriter, r *http.Request) { return } - jobs, err := h.finder.FindJobs(r.Context(), req.Image) + requestedImage := req.Image + if req.Tag != "" { + requestedImage = req.Image + ":" + req.Tag + } + + jobs, err := h.finder.FindJobs(r.Context(), requestedImage) if err != nil { writeJSONError(w, http.StatusInternalServerError, "discovery failed: "+err.Error()) return diff --git a/internal/api/handlers_test.go b/internal/api/handlers_test.go index b62f830..462540a 100644 --- a/internal/api/handlers_test.go +++ b/internal/api/handlers_test.go @@ -21,9 +21,21 @@ import ( type fakeFinder struct { jobs []discovery.Job err error + + // gotImage records the image argument passed to FindJobs, for + // assertions on the exact reference the handler built. + gotImage string + // swarmShaped, when true, makes FindJobs return a Job whose Image + // field carries the received image argument, simulating + // discovery.SwarmDiscovery.FindJobs. + swarmShaped bool } -func (f *fakeFinder) FindJobs(_ context.Context, _ string) ([]discovery.Job, error) { +func (f *fakeFinder) FindJobs(_ context.Context, image string) ([]discovery.Job, error) { + f.gotImage = image + if f.swarmShaped { + return []discovery.Job{{Service: "myapp_web", ServiceID: "svc1", Image: image}}, f.err + } return f.jobs, f.err } @@ -157,6 +169,42 @@ func TestHealthz_503WhenDockerDown(t *testing.T) { require.Equal(t, http.StatusServiceUnavailable, w.Code) } +func TestUpdate_FindJobsCalledWithTaggedImage(t *testing.T) { + finder := &fakeFinder{} + h := api.NewHandlers(finder, &fakeSubmitter{}, &fakePinger{}, "v0.0.0", "abc", "now", nil) + body, _ := json.Marshal(api.UpdateRequest{Image: "registry.example.com/myapp", Tag: "v2"}) + req := httptest.NewRequest(http.MethodPost, "/update", bytes.NewReader(body)) + w := httptest.NewRecorder() + h.Update(w, req) + require.Equal(t, "registry.example.com/myapp:v2", finder.gotImage) +} + +func TestUpdate_FindJobsCalledWithBareImageWhenNoTag(t *testing.T) { + finder := &fakeFinder{} + h := api.NewHandlers(finder, &fakeSubmitter{}, &fakePinger{}, "v0.0.0", "abc", "now", nil) + body, _ := json.Marshal(api.UpdateRequest{Image: "registry.example.com/myapp"}) + req := httptest.NewRequest(http.MethodPost, "/update", bytes.NewReader(body)) + w := httptest.NewRecorder() + h.Update(w, req) + require.Equal(t, "registry.example.com/myapp", finder.gotImage) +} + +func TestUpdate_SwarmModeThreadsTagThroughDiscovery(t *testing.T) { + finder := &fakeFinder{swarmShaped: true} + h := api.NewHandlers(finder, &fakeSubmitter{}, &fakePinger{}, "v0.0.0", "abc", "now", nil) + body, _ := json.Marshal(api.UpdateRequest{Image: "registry.example.com/myapp", Tag: "v2"}) + req := httptest.NewRequest(http.MethodPost, "/update", bytes.NewReader(body)) + w := httptest.NewRecorder() + h.Update(w, req) + require.Equal(t, http.StatusOK, w.Code) + // The fake Finder's returned Job.Image (as SwarmDiscovery would build + // it) reflects the image argument it received from the handler. + // Asserting on the captured argument proves the full request -> + // discovery flow carries the tag through to what would reach the + // SwarmExecutor. + require.Equal(t, "registry.example.com/myapp:v2", finder.gotImage) +} + func TestVersion(t *testing.T) { h := api.NewHandlers(&fakeFinder{}, &fakeSubmitter{}, &fakePinger{}, "v1.2.3", "abcdef", "2026-05-22T00:00:00Z", nil) req := httptest.NewRequest(http.MethodGet, "/version", nil)