package daemon import ( "encoding/json" "net/http" "net/http/httptest" "path/filepath" "testing" "git.cloudinit.dev/coreci/orca/internal/store" ) func newTestServer(t *testing.T) *Server { t.Helper() db, err := store.Open(filepath.Join(t.TempDir(), "test.db")) if err != nil { t.Fatalf("open db: %v", err) } t.Cleanup(func() { _ = db.Close() }) s := NewServer(Options{DB: db, Log: nil, Addr: "127.0.0.1:0"}) s.MarkReady() return s } func TestHealthzReturns200(t *testing.T) { s := newTestServer(t) req := httptest.NewRequest("GET", "/healthz", nil) rr := httptest.NewRecorder() s.mux().ServeHTTP(rr, req) if rr.Code != 200 { t.Errorf("expected 200, got %d", rr.Code) } var body map[string]any _ = json.NewDecoder(rr.Body).Decode(&body) if body["status"] != "alive" { t.Errorf("expected status alive, got %v", body["status"]) } } func TestReadyzReturns200WhenReady(t *testing.T) { s := newTestServer(t) s.MarkReady() req := httptest.NewRequest("GET", "/readyz", nil) rr := httptest.NewRecorder() s.mux().ServeHTTP(rr, req) if rr.Code != 200 { t.Errorf("expected 200, got %d", rr.Code) } } func TestReadyzReturns503WhenNotReady(t *testing.T) { s := newTestServer(t) s.MarkNotReady() req := httptest.NewRequest("GET", "/readyz", nil) rr := httptest.NewRecorder() s.mux().ServeHTTP(rr, req) if rr.Code != 503 { t.Errorf("expected 503, got %d", rr.Code) } } func TestStatusReturns200(t *testing.T) { s := newTestServer(t) s.MarkReady() req := httptest.NewRequest("GET", "/v1/status", nil) rr := httptest.NewRecorder() s.mux().ServeHTTP(rr, req) if rr.Code != 200 { t.Errorf("expected 200, got %d", rr.Code) } var body map[string]any _ = json.NewDecoder(rr.Body).Decode(&body) if body["db"] != "ok" { t.Errorf("expected db ok, got %v", body["db"]) } if body["milestone"] != "v0.1" { t.Errorf("expected milestone v0.1, got %v", body["milestone"]) } } func TestJobsCollectionEmpty(t *testing.T) { s := newTestServer(t) req := httptest.NewRequest("GET", "/v1/jobs", nil) rr := httptest.NewRecorder() s.mux().ServeHTTP(rr, req) if rr.Code != 200 { t.Errorf("expected 200, got %d", rr.Code) } var body map[string]any _ = json.NewDecoder(rr.Body).Decode(&body) if body["count"].(float64) != 0 { t.Errorf("expected count 0, got %v", body["count"]) } } func TestJobsCollectionMethodNotAllowed(t *testing.T) { s := newTestServer(t) req := httptest.NewRequest("PUT", "/v1/jobs", nil) rr := httptest.NewRecorder() s.mux().ServeHTTP(rr, req) if rr.Code != http.StatusMethodNotAllowed { t.Errorf("expected 405, got %d", rr.Code) } } func TestJobsItemNotFound(t *testing.T) { s := newTestServer(t) req := httptest.NewRequest("GET", "/v1/jobs/nonexistent", nil) rr := httptest.NewRecorder() s.mux().ServeHTTP(rr, req) if rr.Code != http.StatusNotFound { t.Errorf("expected 404, got %d", rr.Code) } } func TestJobsItemInvalidID(t *testing.T) { s := newTestServer(t) req := httptest.NewRequest("GET", "/v1/jobs/has%20space", nil) rr := httptest.NewRecorder() s.mux().ServeHTTP(rr, req) if rr.Code != http.StatusBadRequest { t.Errorf("expected 400, got %d", rr.Code) } } func TestNodesCollectionEmpty(t *testing.T) { s := newTestServer(t) req := httptest.NewRequest("GET", "/v1/nodes", nil) rr := httptest.NewRecorder() s.mux().ServeHTTP(rr, req) if rr.Code != 200 { t.Errorf("expected 200, got %d", rr.Code) } var body map[string]any _ = json.NewDecoder(rr.Body).Decode(&body) if body["count"].(float64) != 0 { t.Errorf("expected count 0, got %v", body["count"]) } } func TestTasksCollectionEmpty(t *testing.T) { s := newTestServer(t) req := httptest.NewRequest("GET", "/v1/tasks", nil) rr := httptest.NewRecorder() s.mux().ServeHTTP(rr, req) if rr.Code != 200 { t.Errorf("expected 200, got %d", rr.Code) } } func TestTasksCollectionInvalidLimit(t *testing.T) { s := newTestServer(t) req := httptest.NewRequest("GET", "/v1/tasks?limit=abc", nil) rr := httptest.NewRecorder() s.mux().ServeHTTP(rr, req) if rr.Code != http.StatusBadRequest { t.Errorf("expected 400, got %d", rr.Code) } } func TestValidateID(t *testing.T) { cases := []struct { id string valid bool }{ {"abc-123", true}, {"550e8400-e29b-41d4-a716-446655440000", true}, {"a", true}, {"", false}, {"has space", false}, {"with/slash", false}, {"../etc/passwd", false}, {string([]byte{0x00, 'a'}), false}, } for _, c := range cases { err := validateID(c.id) if (err == nil) != c.valid { t.Errorf("validateID(%q): valid=%v, err=%v", c.id, c.valid, err) } } }