package handlers import ( "encoding/json" "net/http" "sync" "time" "github.com/gin-gonic/gin" ) // TestRunnerHandler serves the admin test-runners result API. // Results are stored in memory — they reset on server restart. type TestRunnerHandler struct { mu sync.RWMutex byID map[string]json.RawMessage // runner-id → last results lastAll json.RawMessage // last "run all" results updated time.Time } func NewTestRunnerHandler() *TestRunnerHandler { return &TestRunnerHandler{ byID: make(map[string]json.RawMessage), } } // StoreResults accepts results JSON from the browser after a test run. // POST /api/v1/admin/test-runners/results func (h *TestRunnerHandler) StoreResults(c *gin.Context) { var body struct { RunnerID string `json:"runner_id"` Results json.RawMessage `json:"results"` } if err := c.ShouldBindJSON(&body); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid JSON body"}) return } if len(body.Results) == 0 { c.JSON(http.StatusBadRequest, gin.H{"error": "results field required"}) return } h.mu.Lock() defer h.mu.Unlock() h.updated = time.Now() if body.RunnerID == "" || body.RunnerID == "all" { h.lastAll = body.Results } else { h.byID[body.RunnerID] = body.Results } c.JSON(http.StatusOK, gin.H{"stored": true, "runner_id": body.RunnerID}) } // GetAllResults returns the last stored "run all" results. // GET /api/v1/admin/test-runners/results func (h *TestRunnerHandler) GetAllResults(c *gin.Context) { h.mu.RLock() defer h.mu.RUnlock() if h.lastAll == nil { c.JSON(http.StatusNotFound, gin.H{"error": "no results available"}) return } c.Data(http.StatusOK, "application/json", h.lastAll) } // GetRunnerResults returns the last stored results for a specific runner. // GET /api/v1/admin/test-runners/results/:id func (h *TestRunnerHandler) GetRunnerResults(c *gin.Context) { id := c.Param("id") h.mu.RLock() defer h.mu.RUnlock() data, ok := h.byID[id] if !ok { c.JSON(http.StatusNotFound, gin.H{"error": "no results for runner: " + id}) return } c.Data(http.StatusOK, "application/json", data) }