package customer import ( "context" "encoding/json" "net/http" "net/http/httptest" "strings" "sync/atomic" "testing" ) func TestEnsureImagePullsMissingImage(t *testing.T) { var present atomic.Bool var pulls atomic.Int32 image := "registry.example.com/neuralhunt/worker:v4.1" ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch { case r.Method == http.MethodGet && strings.HasPrefix(r.URL.Path, "/images/") && strings.HasSuffix(r.URL.Path, "/json"): if !present.Load() { http.NotFound(w, r) return } w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"Id":"sha256:test"}`)) case r.Method == http.MethodPost && r.URL.Path == "/images/create": if got := r.URL.Query().Get("fromImage"); got != image { t.Fatalf("fromImage=%q want %q", got, image) } pulls.Add(1) present.Store(true) w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte("{\"status\":\"Pull complete\"}\n")) default: http.Error(w, "unexpected request", http.StatusBadRequest) } })) defer ts.Close() d := &DockerClient{hc: ts.Client(), base: ts.URL} if err := d.EnsureImage(context.Background(), image, true, ""); err != nil { t.Fatal(err) } if pulls.Load() != 1 { t.Fatalf("pulls=%d want 1", pulls.Load()) } if err := d.EnsureImage(context.Background(), image, true, ""); err != nil { t.Fatal(err) } if pulls.Load() != 1 { t.Fatalf("second ensure pulled again: pulls=%d", pulls.Load()) } } func TestEnsureImageCanRequirePrePulledImage(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.NotFound(w, r) })) defer ts.Close() d := &DockerClient{hc: ts.Client(), base: ts.URL} if err := d.EnsureImage(context.Background(), "neuralhunt-worker:local", false, ""); err == nil || !strings.Contains(err.Error(), "CS_WORKER_AUTO_PULL") { t.Fatalf("unexpected error: %v", err) } } func TestRegistryAuthHeader(t *testing.T) { h, err := RegistryAuthHeader("robot", "token", "registry.example.com") if err != nil { t.Fatal(err) } if h == "" { t.Fatal("expected registry auth header") } if _, err := RegistryAuthHeader("robot", "", "registry.example.com"); err == nil { t.Fatal("incomplete registry credentials should fail") } } func TestCreateWorkerUsesImageEntrypointByDefault(t *testing.T) { var got map[string]any ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/containers/create" { http.Error(w, "unexpected", 400) return } if err := json.NewDecoder(r.Body).Decode(&got); err != nil { t.Fatal(err) } w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"Id":"container-1"}`)) })) defer ts.Close() d := &DockerClient{hc: ts.Client(), base: ts.URL} cfg := WorkerContainerConfig{Image: "neuralhunt-worker:local", Network: "nh", GameURL: "http://app:8080", RegisterURL: "http://cs:8092/internal/workers/register", WorkerID: "wrk_1", RegisterToken: "secret", TaskID: "task_1", BeaconPath: "auto", Volume: "vol_1", Name: "worker-1"} if _, err := d.CreateWorker(context.Background(), cfg); err != nil { t.Fatal(err) } if _, exists := got["Entrypoint"]; exists { t.Fatalf("dedicated worker image should keep its image ENTRYPOINT: %#v", got["Entrypoint"]) } host, _ := got["HostConfig"].(map[string]any) rp, _ := host["RestartPolicy"].(map[string]any) if rp["Name"] != "unless-stopped" { t.Fatalf("worker restart policy=%#v want unless-stopped", rp) } cfg.Name = "worker-2" cfg.Entrypoint = "/app/neuralhunt-client" if _, err := d.CreateWorker(context.Background(), cfg); err != nil { t.Fatal(err) } if _, exists := got["Entrypoint"]; !exists { t.Fatal("compatibility Entrypoint override was not sent") } }