test: add DAG handler, job handler, and runner token tests
This commit is contained in:
247
internal/api/dag_handlers_test.go
Normal file
247
internal/api/dag_handlers_test.go
Normal file
@@ -0,0 +1,247 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/kindredsystems/silo/internal/db"
|
||||
"github.com/kindredsystems/silo/internal/schema"
|
||||
"github.com/kindredsystems/silo/internal/testutil"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
func newDAGTestServer(t *testing.T) *Server {
|
||||
t.Helper()
|
||||
pool := testutil.MustConnectTestPool(t)
|
||||
database := db.NewFromPool(pool)
|
||||
broker := NewBroker(zerolog.Nop())
|
||||
state := NewServerState(zerolog.Nop(), nil, broker)
|
||||
return NewServer(
|
||||
zerolog.Nop(),
|
||||
database,
|
||||
map[string]*schema.Schema{},
|
||||
"",
|
||||
nil, nil, nil, nil, nil,
|
||||
broker, state,
|
||||
nil, "",
|
||||
)
|
||||
}
|
||||
|
||||
func newDAGRouter(s *Server) http.Handler {
|
||||
r := chi.NewRouter()
|
||||
r.Route("/api/items/{partNumber}", func(r chi.Router) {
|
||||
r.Get("/dag", s.HandleGetDAG)
|
||||
r.Get("/dag/forward-cone/{nodeKey}", s.HandleGetForwardCone)
|
||||
r.Get("/dag/dirty", s.HandleGetDirtySubgraph)
|
||||
r.Put("/dag", s.HandleSyncDAG)
|
||||
r.Post("/dag/mark-dirty/{nodeKey}", s.HandleMarkDirty)
|
||||
})
|
||||
return r
|
||||
}
|
||||
|
||||
func TestHandleGetDAG_Empty(t *testing.T) {
|
||||
s := newDAGTestServer(t)
|
||||
r := newDAGRouter(s)
|
||||
|
||||
// Create an item
|
||||
item := &db.Item{PartNumber: "DAG-TEST-001", ItemType: "part", Description: "DAG test"}
|
||||
if err := s.items.Create(context.Background(), item, nil); err != nil {
|
||||
t.Fatalf("creating item: %v", err)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", "/api/items/DAG-TEST-001/dag", nil)
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var resp map[string]any
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
if resp["part_number"] != "DAG-TEST-001" {
|
||||
t.Errorf("expected part_number DAG-TEST-001, got %v", resp["part_number"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleSyncDAG(t *testing.T) {
|
||||
s := newDAGTestServer(t)
|
||||
r := newDAGRouter(s)
|
||||
|
||||
// Create an item with a revision
|
||||
item := &db.Item{PartNumber: "DAG-SYNC-001", ItemType: "part", Description: "sync test"}
|
||||
if err := s.items.Create(context.Background(), item, nil); err != nil {
|
||||
t.Fatalf("creating item: %v", err)
|
||||
}
|
||||
|
||||
// Sync a feature tree
|
||||
body := `{
|
||||
"nodes": [
|
||||
{"node_key": "Sketch001", "node_type": "sketch"},
|
||||
{"node_key": "Pad001", "node_type": "pad"},
|
||||
{"node_key": "Fillet001", "node_type": "fillet"}
|
||||
],
|
||||
"edges": [
|
||||
{"source_key": "Sketch001", "target_key": "Pad001", "edge_type": "depends_on"},
|
||||
{"source_key": "Pad001", "target_key": "Fillet001", "edge_type": "depends_on"}
|
||||
]
|
||||
}`
|
||||
|
||||
req := httptest.NewRequest("PUT", "/api/items/DAG-SYNC-001/dag", strings.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var resp map[string]any
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
if resp["node_count"] != float64(3) {
|
||||
t.Errorf("expected 3 nodes, got %v", resp["node_count"])
|
||||
}
|
||||
if resp["edge_count"] != float64(2) {
|
||||
t.Errorf("expected 2 edges, got %v", resp["edge_count"])
|
||||
}
|
||||
|
||||
// Verify we can read the DAG back
|
||||
req2 := httptest.NewRequest("GET", "/api/items/DAG-SYNC-001/dag", nil)
|
||||
w2 := httptest.NewRecorder()
|
||||
r.ServeHTTP(w2, req2)
|
||||
|
||||
if w2.Code != http.StatusOK {
|
||||
t.Fatalf("GET dag: expected 200, got %d", w2.Code)
|
||||
}
|
||||
var dagResp map[string]any
|
||||
json.Unmarshal(w2.Body.Bytes(), &dagResp)
|
||||
nodes, ok := dagResp["nodes"].([]any)
|
||||
if !ok || len(nodes) != 3 {
|
||||
t.Errorf("expected 3 nodes in GET, got %v", dagResp["nodes"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleForwardCone(t *testing.T) {
|
||||
s := newDAGTestServer(t)
|
||||
r := newDAGRouter(s)
|
||||
|
||||
item := &db.Item{PartNumber: "DAG-CONE-001", ItemType: "part", Description: "cone test"}
|
||||
if err := s.items.Create(context.Background(), item, nil); err != nil {
|
||||
t.Fatalf("creating item: %v", err)
|
||||
}
|
||||
|
||||
// Sync a linear chain: A -> B -> C
|
||||
body := `{
|
||||
"nodes": [
|
||||
{"node_key": "A", "node_type": "sketch"},
|
||||
{"node_key": "B", "node_type": "pad"},
|
||||
{"node_key": "C", "node_type": "fillet"}
|
||||
],
|
||||
"edges": [
|
||||
{"source_key": "A", "target_key": "B"},
|
||||
{"source_key": "B", "target_key": "C"}
|
||||
]
|
||||
}`
|
||||
req := httptest.NewRequest("PUT", "/api/items/DAG-CONE-001/dag", strings.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("sync: %d %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
// Forward cone from A should include B and C
|
||||
req2 := httptest.NewRequest("GET", "/api/items/DAG-CONE-001/dag/forward-cone/A", nil)
|
||||
w2 := httptest.NewRecorder()
|
||||
r.ServeHTTP(w2, req2)
|
||||
|
||||
if w2.Code != http.StatusOK {
|
||||
t.Fatalf("forward-cone: %d %s", w2.Code, w2.Body.String())
|
||||
}
|
||||
|
||||
var resp map[string]any
|
||||
json.Unmarshal(w2.Body.Bytes(), &resp)
|
||||
cone, ok := resp["cone"].([]any)
|
||||
if !ok || len(cone) != 2 {
|
||||
t.Errorf("expected 2 nodes in forward cone, got %v", resp["cone"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleMarkDirty(t *testing.T) {
|
||||
s := newDAGTestServer(t)
|
||||
r := newDAGRouter(s)
|
||||
|
||||
item := &db.Item{PartNumber: "DAG-DIRTY-001", ItemType: "part", Description: "dirty test"}
|
||||
if err := s.items.Create(context.Background(), item, nil); err != nil {
|
||||
t.Fatalf("creating item: %v", err)
|
||||
}
|
||||
|
||||
// Sync: A -> B -> C
|
||||
body := `{
|
||||
"nodes": [
|
||||
{"node_key": "X", "node_type": "sketch"},
|
||||
{"node_key": "Y", "node_type": "pad"},
|
||||
{"node_key": "Z", "node_type": "fillet"}
|
||||
],
|
||||
"edges": [
|
||||
{"source_key": "X", "target_key": "Y"},
|
||||
{"source_key": "Y", "target_key": "Z"}
|
||||
]
|
||||
}`
|
||||
req := httptest.NewRequest("PUT", "/api/items/DAG-DIRTY-001/dag", strings.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("sync: %d %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
// Mark X dirty — should propagate to Y and Z
|
||||
req2 := httptest.NewRequest("POST", "/api/items/DAG-DIRTY-001/dag/mark-dirty/X", nil)
|
||||
w2 := httptest.NewRecorder()
|
||||
r.ServeHTTP(w2, req2)
|
||||
|
||||
if w2.Code != http.StatusOK {
|
||||
t.Fatalf("mark-dirty: %d %s", w2.Code, w2.Body.String())
|
||||
}
|
||||
|
||||
var resp map[string]any
|
||||
json.Unmarshal(w2.Body.Bytes(), &resp)
|
||||
affected := resp["nodes_affected"].(float64)
|
||||
if affected != 3 {
|
||||
t.Errorf("expected 3 nodes affected, got %v", affected)
|
||||
}
|
||||
|
||||
// Verify dirty subgraph
|
||||
req3 := httptest.NewRequest("GET", "/api/items/DAG-DIRTY-001/dag/dirty", nil)
|
||||
w3 := httptest.NewRecorder()
|
||||
r.ServeHTTP(w3, req3)
|
||||
|
||||
if w3.Code != http.StatusOK {
|
||||
t.Fatalf("dirty: %d %s", w3.Code, w3.Body.String())
|
||||
}
|
||||
var dirtyResp map[string]any
|
||||
json.Unmarshal(w3.Body.Bytes(), &dirtyResp)
|
||||
dirtyNodes, ok := dirtyResp["nodes"].([]any)
|
||||
if !ok || len(dirtyNodes) != 3 {
|
||||
t.Errorf("expected 3 dirty nodes, got %v", dirtyResp["nodes"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleGetDAG_NotFound(t *testing.T) {
|
||||
s := newDAGTestServer(t)
|
||||
r := newDAGRouter(s)
|
||||
|
||||
req := httptest.NewRequest("GET", "/api/items/NONEXISTENT-999/dag", nil)
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusNotFound {
|
||||
t.Errorf("expected 404, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
338
internal/api/job_handlers_test.go
Normal file
338
internal/api/job_handlers_test.go
Normal file
@@ -0,0 +1,338 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/kindredsystems/silo/internal/db"
|
||||
"github.com/kindredsystems/silo/internal/schema"
|
||||
"github.com/kindredsystems/silo/internal/testutil"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
func newJobTestServer(t *testing.T) *Server {
|
||||
t.Helper()
|
||||
pool := testutil.MustConnectTestPool(t)
|
||||
database := db.NewFromPool(pool)
|
||||
broker := NewBroker(zerolog.Nop())
|
||||
state := NewServerState(zerolog.Nop(), nil, broker)
|
||||
return NewServer(
|
||||
zerolog.Nop(),
|
||||
database,
|
||||
map[string]*schema.Schema{},
|
||||
"",
|
||||
nil, nil, nil, nil, nil,
|
||||
broker, state,
|
||||
nil, "",
|
||||
)
|
||||
}
|
||||
|
||||
func newJobRouter(s *Server) http.Handler {
|
||||
r := chi.NewRouter()
|
||||
r.Route("/api/jobs", func(r chi.Router) {
|
||||
r.Get("/", s.HandleListJobs)
|
||||
r.Get("/{jobID}", s.HandleGetJob)
|
||||
r.Get("/{jobID}/logs", s.HandleGetJobLogs)
|
||||
r.Post("/", s.HandleCreateJob)
|
||||
r.Post("/{jobID}/cancel", s.HandleCancelJob)
|
||||
})
|
||||
r.Route("/api/job-definitions", func(r chi.Router) {
|
||||
r.Get("/", s.HandleListJobDefinitions)
|
||||
r.Get("/{name}", s.HandleGetJobDefinition)
|
||||
})
|
||||
r.Route("/api/runners", func(r chi.Router) {
|
||||
r.Get("/", s.HandleListRunners)
|
||||
r.Post("/", s.HandleRegisterRunner)
|
||||
r.Delete("/{runnerID}", s.HandleDeleteRunner)
|
||||
})
|
||||
return r
|
||||
}
|
||||
|
||||
func seedJobDefinition(t *testing.T, s *Server) *db.JobDefinitionRecord {
|
||||
t.Helper()
|
||||
rec := &db.JobDefinitionRecord{
|
||||
Name: "test-validate",
|
||||
Version: 1,
|
||||
TriggerType: "manual",
|
||||
ScopeType: "item",
|
||||
ComputeType: "validate",
|
||||
RunnerTags: []string{"create"},
|
||||
TimeoutSeconds: 300,
|
||||
MaxRetries: 1,
|
||||
Priority: 100,
|
||||
Definition: map[string]any{"compute": map[string]any{"command": "create-validate"}},
|
||||
Enabled: true,
|
||||
}
|
||||
if err := s.jobs.UpsertDefinition(context.Background(), rec); err != nil {
|
||||
t.Fatalf("seeding job definition: %v", err)
|
||||
}
|
||||
return rec
|
||||
}
|
||||
|
||||
func TestHandleListJobDefinitions(t *testing.T) {
|
||||
s := newJobTestServer(t)
|
||||
r := newJobRouter(s)
|
||||
|
||||
seedJobDefinition(t, s)
|
||||
|
||||
req := httptest.NewRequest("GET", "/api/job-definitions", nil)
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var defs []map[string]any
|
||||
json.Unmarshal(w.Body.Bytes(), &defs)
|
||||
if len(defs) == 0 {
|
||||
t.Error("expected at least one definition")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleGetJobDefinition(t *testing.T) {
|
||||
s := newJobTestServer(t)
|
||||
r := newJobRouter(s)
|
||||
|
||||
seedJobDefinition(t, s)
|
||||
|
||||
req := httptest.NewRequest("GET", "/api/job-definitions/test-validate", nil)
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var def map[string]any
|
||||
json.Unmarshal(w.Body.Bytes(), &def)
|
||||
if def["name"] != "test-validate" {
|
||||
t.Errorf("expected name test-validate, got %v", def["name"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleCreateAndGetJob(t *testing.T) {
|
||||
s := newJobTestServer(t)
|
||||
r := newJobRouter(s)
|
||||
|
||||
seedJobDefinition(t, s)
|
||||
|
||||
// Create a job
|
||||
body := `{"definition_name": "test-validate"}`
|
||||
req := httptest.NewRequest("POST", "/api/jobs", strings.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusCreated {
|
||||
t.Fatalf("create: expected 201, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var job map[string]any
|
||||
json.Unmarshal(w.Body.Bytes(), &job)
|
||||
jobID := job["ID"].(string)
|
||||
if jobID == "" {
|
||||
t.Fatal("job ID is empty")
|
||||
}
|
||||
|
||||
// Get the job
|
||||
req2 := httptest.NewRequest("GET", "/api/jobs/"+jobID, nil)
|
||||
w2 := httptest.NewRecorder()
|
||||
r.ServeHTTP(w2, req2)
|
||||
|
||||
if w2.Code != http.StatusOK {
|
||||
t.Fatalf("get: expected 200, got %d: %s", w2.Code, w2.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleCancelJob(t *testing.T) {
|
||||
s := newJobTestServer(t)
|
||||
r := newJobRouter(s)
|
||||
|
||||
seedJobDefinition(t, s)
|
||||
|
||||
// Create a job
|
||||
body := `{"definition_name": "test-validate"}`
|
||||
req := httptest.NewRequest("POST", "/api/jobs", strings.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
var job map[string]any
|
||||
json.Unmarshal(w.Body.Bytes(), &job)
|
||||
jobID := job["ID"].(string)
|
||||
|
||||
// Cancel the job
|
||||
req2 := httptest.NewRequest("POST", "/api/jobs/"+jobID+"/cancel", nil)
|
||||
w2 := httptest.NewRecorder()
|
||||
r.ServeHTTP(w2, req2)
|
||||
|
||||
if w2.Code != http.StatusOK {
|
||||
t.Fatalf("cancel: expected 200, got %d: %s", w2.Code, w2.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleListJobs(t *testing.T) {
|
||||
s := newJobTestServer(t)
|
||||
r := newJobRouter(s)
|
||||
|
||||
seedJobDefinition(t, s)
|
||||
|
||||
// Create a job
|
||||
body := `{"definition_name": "test-validate"}`
|
||||
req := httptest.NewRequest("POST", "/api/jobs", strings.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
// List jobs
|
||||
req2 := httptest.NewRequest("GET", "/api/jobs", nil)
|
||||
w2 := httptest.NewRecorder()
|
||||
r.ServeHTTP(w2, req2)
|
||||
|
||||
if w2.Code != http.StatusOK {
|
||||
t.Fatalf("list: expected 200, got %d: %s", w2.Code, w2.Body.String())
|
||||
}
|
||||
|
||||
var jobs []map[string]any
|
||||
json.Unmarshal(w2.Body.Bytes(), &jobs)
|
||||
if len(jobs) == 0 {
|
||||
t.Error("expected at least one job")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleListJobs_FilterByStatus(t *testing.T) {
|
||||
s := newJobTestServer(t)
|
||||
r := newJobRouter(s)
|
||||
|
||||
seedJobDefinition(t, s)
|
||||
|
||||
// Create a job
|
||||
body := `{"definition_name": "test-validate"}`
|
||||
req := httptest.NewRequest("POST", "/api/jobs", strings.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
// Filter by pending
|
||||
req2 := httptest.NewRequest("GET", "/api/jobs?status=pending", nil)
|
||||
w2 := httptest.NewRecorder()
|
||||
r.ServeHTTP(w2, req2)
|
||||
|
||||
if w2.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", w2.Code)
|
||||
}
|
||||
|
||||
var jobs []map[string]any
|
||||
json.Unmarshal(w2.Body.Bytes(), &jobs)
|
||||
if len(jobs) == 0 {
|
||||
t.Error("expected pending jobs")
|
||||
}
|
||||
|
||||
// Filter by completed (should be empty)
|
||||
req3 := httptest.NewRequest("GET", "/api/jobs?status=completed", nil)
|
||||
w3 := httptest.NewRecorder()
|
||||
r.ServeHTTP(w3, req3)
|
||||
|
||||
var completedJobs []map[string]any
|
||||
json.Unmarshal(w3.Body.Bytes(), &completedJobs)
|
||||
if len(completedJobs) != 0 {
|
||||
t.Errorf("expected no completed jobs, got %d", len(completedJobs))
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleRegisterAndListRunners(t *testing.T) {
|
||||
s := newJobTestServer(t)
|
||||
r := newJobRouter(s)
|
||||
|
||||
// Register a runner
|
||||
body := `{"name": "test-runner-1", "tags": ["create", "linux"]}`
|
||||
req := httptest.NewRequest("POST", "/api/runners", strings.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusCreated {
|
||||
t.Fatalf("register: expected 201, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var resp map[string]any
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
if resp["token"] == nil || resp["token"] == "" {
|
||||
t.Error("expected a token in response")
|
||||
}
|
||||
if !strings.HasPrefix(resp["token"].(string), "silo_runner_") {
|
||||
t.Errorf("expected token to start with silo_runner_, got %s", resp["token"])
|
||||
}
|
||||
|
||||
// List runners
|
||||
req2 := httptest.NewRequest("GET", "/api/runners", nil)
|
||||
w2 := httptest.NewRecorder()
|
||||
r.ServeHTTP(w2, req2)
|
||||
|
||||
if w2.Code != http.StatusOK {
|
||||
t.Fatalf("list: expected 200, got %d", w2.Code)
|
||||
}
|
||||
|
||||
var runners []map[string]any
|
||||
json.Unmarshal(w2.Body.Bytes(), &runners)
|
||||
if len(runners) == 0 {
|
||||
t.Error("expected at least one runner")
|
||||
}
|
||||
// Token hash should not be exposed
|
||||
for _, runner := range runners {
|
||||
if runner["token_hash"] != nil {
|
||||
t.Error("token_hash should not be in response")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleDeleteRunner(t *testing.T) {
|
||||
s := newJobTestServer(t)
|
||||
r := newJobRouter(s)
|
||||
|
||||
// Register a runner
|
||||
body := `{"name": "test-runner-delete", "tags": ["create"]}`
|
||||
req := httptest.NewRequest("POST", "/api/runners", strings.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
var resp map[string]any
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
runnerID := resp["id"].(string)
|
||||
|
||||
// Delete the runner
|
||||
req2 := httptest.NewRequest("DELETE", "/api/runners/"+runnerID, nil)
|
||||
w2 := httptest.NewRecorder()
|
||||
r.ServeHTTP(w2, req2)
|
||||
|
||||
if w2.Code != http.StatusNoContent {
|
||||
t.Fatalf("delete: expected 204, got %d: %s", w2.Code, w2.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateRunnerToken(t *testing.T) {
|
||||
raw, hash, prefix := generateRunnerToken()
|
||||
|
||||
if !strings.HasPrefix(raw, "silo_runner_") {
|
||||
t.Errorf("raw token should start with silo_runner_, got %s", raw[:20])
|
||||
}
|
||||
if len(hash) != 64 {
|
||||
t.Errorf("hash should be 64 hex chars, got %d", len(hash))
|
||||
}
|
||||
if len(prefix) != 20 {
|
||||
t.Errorf("prefix should be 20 chars, got %d: %s", len(prefix), prefix)
|
||||
}
|
||||
|
||||
// Two tokens should be different
|
||||
raw2, _, _ := generateRunnerToken()
|
||||
if raw == raw2 {
|
||||
t.Error("two generated tokens should be different")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user