548 lines
16 KiB
Go
548 lines
16 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"log/slog"
|
|
"net/http"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/iliaivanov/spec-kit-remote/cmd/dev-pod-api/internal/k8s"
|
|
"github.com/iliaivanov/spec-kit-remote/cmd/dev-pod-api/internal/model"
|
|
"github.com/iliaivanov/spec-kit-remote/cmd/dev-pod-api/internal/store"
|
|
)
|
|
|
|
type mockRunnerStore struct {
|
|
mu sync.Mutex
|
|
runners map[string]*model.Runner
|
|
}
|
|
|
|
func newMockRunnerStore() *mockRunnerStore {
|
|
return &mockRunnerStore{runners: make(map[string]*model.Runner)}
|
|
}
|
|
|
|
func (m *mockRunnerStore) CreateRunner(_ context.Context, r *model.Runner) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if _, exists := m.runners[r.ID]; exists {
|
|
return fmt.Errorf("runner %q: %w", r.ID, store.ErrDuplicate)
|
|
}
|
|
for _, existing := range m.runners {
|
|
if r.WebhookDeliveryID != "" && existing.WebhookDeliveryID == r.WebhookDeliveryID {
|
|
return fmt.Errorf("runner %q: %w", r.ID, store.ErrDuplicate)
|
|
}
|
|
}
|
|
copy := *r
|
|
m.runners[r.ID] = ©
|
|
return nil
|
|
}
|
|
|
|
func (m *mockRunnerStore) GetRunner(_ context.Context, id string) (*model.Runner, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
r, ok := m.runners[id]
|
|
if !ok {
|
|
return nil, fmt.Errorf("runner %q: %w", id, store.ErrNotFound)
|
|
}
|
|
copy := *r
|
|
return ©, nil
|
|
}
|
|
|
|
func (m *mockRunnerStore) ListRunners(_ context.Context, userFilter, statusFilter string) ([]model.Runner, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
var result []model.Runner
|
|
for _, r := range m.runners {
|
|
if userFilter != "" && r.User != userFilter {
|
|
continue
|
|
}
|
|
if statusFilter != "" && string(r.Status) != statusFilter {
|
|
continue
|
|
}
|
|
result = append(result, *r)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (m *mockRunnerStore) UpdateRunnerStatus(_ context.Context, id string, newStatus model.RunnerStatus, forgejoRunnerID string) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
r, ok := m.runners[id]
|
|
if !ok {
|
|
return fmt.Errorf("runner %q: %w", id, store.ErrNotFound)
|
|
}
|
|
if !r.Status.CanTransitionTo(newStatus) {
|
|
return fmt.Errorf("invalid transition from %s to %s", r.Status, newStatus)
|
|
}
|
|
r.Status = newStatus
|
|
if forgejoRunnerID != "" {
|
|
r.ForgejoRunnerID = forgejoRunnerID
|
|
}
|
|
now := time.Now().UTC()
|
|
if newStatus == model.RunnerStatusJobClaimed {
|
|
r.ClaimedAt = &now
|
|
}
|
|
if newStatus.IsTerminal() {
|
|
r.CompletedAt = &now
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *mockRunnerStore) DeleteRunner(_ context.Context, id string) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if _, ok := m.runners[id]; !ok {
|
|
return fmt.Errorf("runner %q: %w", id, store.ErrNotFound)
|
|
}
|
|
delete(m.runners, id)
|
|
return nil
|
|
}
|
|
|
|
func (m *mockRunnerStore) IsDeliveryProcessed(_ context.Context, deliveryID string) (bool, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if deliveryID == "" {
|
|
return false, nil
|
|
}
|
|
for _, r := range m.runners {
|
|
if r.WebhookDeliveryID == deliveryID {
|
|
return true, nil
|
|
}
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
func (m *mockRunnerStore) GetStaleRunners(_ context.Context, ttl time.Duration) ([]model.Runner, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
cutoff := time.Now().UTC().Add(-ttl)
|
|
var result []model.Runner
|
|
for _, r := range m.runners {
|
|
if r.CreatedAt.Before(cutoff) && !r.Status.IsTerminal() && r.Status != model.RunnerStatusCleanupPending {
|
|
result = append(result, *r)
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
type mockRunnerPodManager struct {
|
|
mu sync.Mutex
|
|
pods map[string]string // runnerID -> podName
|
|
createErr error
|
|
}
|
|
|
|
func newMockRunnerPodManager() *mockRunnerPodManager {
|
|
return &mockRunnerPodManager{pods: make(map[string]string)}
|
|
}
|
|
|
|
func (m *mockRunnerPodManager) CreateRunnerPod(_ context.Context, opts k8s.CreateRunnerPodOpts) (string, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if m.createErr != nil {
|
|
return "", m.createErr
|
|
}
|
|
podName := model.RunnerPodName(opts.RunnerID)
|
|
m.pods[opts.RunnerID] = podName
|
|
return podName, nil
|
|
}
|
|
|
|
func (m *mockRunnerPodManager) DeleteRunnerPod(_ context.Context, _, podName string) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
for k, v := range m.pods {
|
|
if v == podName {
|
|
delete(m.pods, k)
|
|
return nil
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func newRunnerTestRouter() (*mockKeyValidator, *mockUserStore, *mockRunnerStore, *mockRunnerPodManager, http.Handler) {
|
|
kv := newMockValidator()
|
|
us := newMockUserStore()
|
|
rs := newMockRunnerStore()
|
|
rp := newMockRunnerPodManager()
|
|
fm := newMockForgejoManager()
|
|
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
|
|
srv := &Server{
|
|
Store: kv,
|
|
K8s: newMockPodManager(),
|
|
Users: us,
|
|
Usage: newMockUsageStore(),
|
|
Runners: rs,
|
|
RunnerPods: rp,
|
|
Forgejo: fm,
|
|
Logger: logger,
|
|
GenerateKey: mockGenerateKey,
|
|
}
|
|
return kv, us, rs, rp, NewRouter(srv)
|
|
}
|
|
|
|
func TestCreateRunner_Success(t *testing.T) {
|
|
kv, us, rs, rp, router := newRunnerTestRouter()
|
|
user := &model.User{ID: "alice", Quota: model.DefaultQuota()}
|
|
kv.addKey("dpk_test", user, model.RoleUser)
|
|
us.addUser(user)
|
|
|
|
body := `{"user":"alice","repo":"alice/myrepo","branch":"main","tools":"go","task":"build it"}`
|
|
rr := doRequest(router, http.MethodPost, "/api/v1/runners", body, "dpk_test")
|
|
|
|
if rr.Code != http.StatusCreated {
|
|
t.Fatalf("expected 201, got %d: %s", rr.Code, rr.Body.String())
|
|
}
|
|
|
|
var runner model.Runner
|
|
decodeJSON(t, rr, &runner)
|
|
if runner.User != "alice" {
|
|
t.Errorf("expected user alice, got %s", runner.User)
|
|
}
|
|
if runner.RepoURL != "alice/myrepo" {
|
|
t.Errorf("expected repo alice/myrepo, got %s", runner.RepoURL)
|
|
}
|
|
if runner.Status != model.RunnerStatusPodCreating {
|
|
t.Errorf("expected status pod_creating, got %s", runner.Status)
|
|
}
|
|
|
|
rs.mu.Lock()
|
|
count := len(rs.runners)
|
|
rs.mu.Unlock()
|
|
if count != 1 {
|
|
t.Errorf("expected 1 runner in store, got %d", count)
|
|
}
|
|
|
|
rp.mu.Lock()
|
|
podCount := len(rp.pods)
|
|
rp.mu.Unlock()
|
|
if podCount != 1 {
|
|
t.Errorf("expected 1 pod created, got %d", podCount)
|
|
}
|
|
}
|
|
|
|
func TestCreateRunner_DedupeWebhook(t *testing.T) {
|
|
kv, us, rs, _, router := newRunnerTestRouter()
|
|
user := &model.User{ID: "alice", Quota: model.DefaultQuota()}
|
|
kv.addKey("dpk_test", user, model.RoleUser)
|
|
us.addUser(user)
|
|
|
|
body := `{"user":"alice","repo":"alice/myrepo","webhook_delivery_id":"delivery-xyz"}`
|
|
rr := doRequest(router, http.MethodPost, "/api/v1/runners", body, "dpk_test")
|
|
if rr.Code != http.StatusCreated {
|
|
t.Fatalf("first create: expected 201, got %d: %s", rr.Code, rr.Body.String())
|
|
}
|
|
|
|
rr2 := doRequest(router, http.MethodPost, "/api/v1/runners", body, "dpk_test")
|
|
if rr2.Code != http.StatusConflict {
|
|
t.Fatalf("dupe: expected 409, got %d: %s", rr2.Code, rr2.Body.String())
|
|
}
|
|
|
|
rs.mu.Lock()
|
|
count := len(rs.runners)
|
|
rs.mu.Unlock()
|
|
if count != 1 {
|
|
t.Errorf("expected exactly 1 runner after dedupe, got %d", count)
|
|
}
|
|
}
|
|
|
|
func TestCreateRunner_ForbiddenOtherUser(t *testing.T) {
|
|
kv, us, _, _, router := newRunnerTestRouter()
|
|
user := &model.User{ID: "alice", Quota: model.DefaultQuota()}
|
|
kv.addKey("dpk_test", user, model.RoleUser)
|
|
us.addUser(user)
|
|
us.addUser(&model.User{ID: "bob", Quota: model.DefaultQuota()})
|
|
|
|
body := `{"user":"bob","repo":"bob/repo"}`
|
|
rr := doRequest(router, http.MethodPost, "/api/v1/runners", body, "dpk_test")
|
|
if rr.Code != http.StatusForbidden {
|
|
t.Fatalf("expected 403, got %d: %s", rr.Code, rr.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestCreateRunner_AdminCanCreateForOthers(t *testing.T) {
|
|
kv, us, _, _, router := newRunnerTestRouter()
|
|
admin := &model.User{ID: "admin-user", Quota: model.DefaultQuota()}
|
|
bob := &model.User{ID: "bob", Quota: model.DefaultQuota()}
|
|
kv.addKey("dpk_admin", admin, model.RoleAdmin)
|
|
us.addUser(admin)
|
|
us.addUser(bob)
|
|
|
|
body := `{"user":"bob","repo":"bob/repo"}`
|
|
rr := doRequest(router, http.MethodPost, "/api/v1/runners", body, "dpk_admin")
|
|
if rr.Code != http.StatusCreated {
|
|
t.Fatalf("expected 201, got %d: %s", rr.Code, rr.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestCreateRunner_UserNotFound(t *testing.T) {
|
|
kv, _, _, _, router := newRunnerTestRouter()
|
|
user := &model.User{ID: "alice", Quota: model.DefaultQuota()}
|
|
kv.addKey("dpk_test", user, model.RoleUser)
|
|
|
|
body := `{"user":"alice","repo":"alice/repo"}`
|
|
rr := doRequest(router, http.MethodPost, "/api/v1/runners", body, "dpk_test")
|
|
if rr.Code != http.StatusNotFound {
|
|
t.Fatalf("expected 404, got %d: %s", rr.Code, rr.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestCreateRunner_InvalidBody(t *testing.T) {
|
|
kv, _, _, _, router := newRunnerTestRouter()
|
|
user := &model.User{ID: "alice", Quota: model.DefaultQuota()}
|
|
kv.addKey("dpk_test", user, model.RoleUser)
|
|
|
|
rr := doRequest(router, http.MethodPost, "/api/v1/runners", "not json", "dpk_test")
|
|
if rr.Code != http.StatusBadRequest {
|
|
t.Fatalf("expected 400, got %d", rr.Code)
|
|
}
|
|
}
|
|
|
|
func TestCreateRunner_ValidationError(t *testing.T) {
|
|
kv, _, _, _, router := newRunnerTestRouter()
|
|
user := &model.User{ID: "alice", Quota: model.DefaultQuota()}
|
|
kv.addKey("dpk_test", user, model.RoleUser)
|
|
|
|
rr := doRequest(router, http.MethodPost, "/api/v1/runners", `{"user":"alice"}`, "dpk_test")
|
|
if rr.Code != http.StatusBadRequest {
|
|
t.Fatalf("expected 400, got %d: %s", rr.Code, rr.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestCreateRunner_PodCreationFails(t *testing.T) {
|
|
kv := newMockValidator()
|
|
us := newMockUserStore()
|
|
rs := newMockRunnerStore()
|
|
rp := newMockRunnerPodManager()
|
|
rp.createErr = fmt.Errorf("k8s error")
|
|
fm := newMockForgejoManager()
|
|
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
|
|
srv := &Server{
|
|
Store: kv,
|
|
K8s: newMockPodManager(),
|
|
Users: us,
|
|
Usage: newMockUsageStore(),
|
|
Runners: rs,
|
|
RunnerPods: rp,
|
|
Forgejo: fm,
|
|
Logger: logger,
|
|
GenerateKey: mockGenerateKey,
|
|
}
|
|
router := NewRouter(srv)
|
|
|
|
user := &model.User{ID: "alice", Quota: model.DefaultQuota()}
|
|
kv.addKey("dpk_test", user, model.RoleUser)
|
|
us.addUser(user)
|
|
|
|
body := `{"user":"alice","repo":"alice/repo"}`
|
|
rr := doRequest(router, http.MethodPost, "/api/v1/runners", body, "dpk_test")
|
|
if rr.Code != http.StatusInternalServerError {
|
|
t.Fatalf("expected 500, got %d: %s", rr.Code, rr.Body.String())
|
|
}
|
|
|
|
rs.mu.Lock()
|
|
for _, r := range rs.runners {
|
|
if r.Status != model.RunnerStatusFailed {
|
|
t.Errorf("expected runner status failed after pod creation failure, got %s", r.Status)
|
|
}
|
|
}
|
|
rs.mu.Unlock()
|
|
}
|
|
|
|
func TestListRunners_UserSeesOwn(t *testing.T) {
|
|
kv, us, rs, _, router := newRunnerTestRouter()
|
|
alice := &model.User{ID: "alice", Quota: model.DefaultQuota()}
|
|
kv.addKey("dpk_alice", alice, model.RoleUser)
|
|
us.addUser(alice)
|
|
|
|
rs.mu.Lock()
|
|
rs.runners["r1"] = &model.Runner{ID: "r1", User: "alice", Status: model.RunnerStatusReceived}
|
|
rs.runners["r2"] = &model.Runner{ID: "r2", User: "bob", Status: model.RunnerStatusReceived}
|
|
rs.mu.Unlock()
|
|
|
|
rr := doRequest(router, http.MethodGet, "/api/v1/runners", "", "dpk_alice")
|
|
if rr.Code != http.StatusOK {
|
|
t.Fatalf("expected 200, got %d: %s", rr.Code, rr.Body.String())
|
|
}
|
|
|
|
var runners []model.Runner
|
|
decodeJSON(t, rr, &runners)
|
|
if len(runners) != 1 {
|
|
t.Fatalf("expected 1 runner, got %d", len(runners))
|
|
}
|
|
if runners[0].User != "alice" {
|
|
t.Errorf("expected alice's runner, got %s", runners[0].User)
|
|
}
|
|
}
|
|
|
|
func TestListRunners_AdminSeesAll(t *testing.T) {
|
|
kv, us, rs, _, router := newRunnerTestRouter()
|
|
admin := &model.User{ID: "admin-user", Quota: model.DefaultQuota()}
|
|
kv.addKey("dpk_admin", admin, model.RoleAdmin)
|
|
us.addUser(admin)
|
|
|
|
rs.mu.Lock()
|
|
rs.runners["r1"] = &model.Runner{ID: "r1", User: "alice", Status: model.RunnerStatusReceived}
|
|
rs.runners["r2"] = &model.Runner{ID: "r2", User: "bob", Status: model.RunnerStatusReceived}
|
|
rs.mu.Unlock()
|
|
|
|
rr := doRequest(router, http.MethodGet, "/api/v1/runners", "", "dpk_admin")
|
|
if rr.Code != http.StatusOK {
|
|
t.Fatalf("expected 200, got %d: %s", rr.Code, rr.Body.String())
|
|
}
|
|
|
|
var runners []model.Runner
|
|
decodeJSON(t, rr, &runners)
|
|
if len(runners) != 2 {
|
|
t.Fatalf("expected 2 runners, got %d", len(runners))
|
|
}
|
|
}
|
|
|
|
func TestDeleteRunner_Success(t *testing.T) {
|
|
kv, us, rs, _, router := newRunnerTestRouter()
|
|
user := &model.User{ID: "alice", Quota: model.DefaultQuota()}
|
|
kv.addKey("dpk_test", user, model.RoleUser)
|
|
us.addUser(user)
|
|
|
|
rs.mu.Lock()
|
|
rs.runners["r1"] = &model.Runner{ID: "r1", User: "alice", PodName: "dev-pod-r1", Status: model.RunnerStatusReceived}
|
|
rs.mu.Unlock()
|
|
|
|
rr := doRequest(router, http.MethodDelete, "/api/v1/runners/r1", "", "dpk_test")
|
|
if rr.Code != http.StatusNoContent {
|
|
t.Fatalf("expected 204, got %d: %s", rr.Code, rr.Body.String())
|
|
}
|
|
|
|
rs.mu.Lock()
|
|
count := len(rs.runners)
|
|
rs.mu.Unlock()
|
|
if count != 0 {
|
|
t.Errorf("expected 0 runners after delete, got %d", count)
|
|
}
|
|
}
|
|
|
|
func TestDeleteRunner_NotFound(t *testing.T) {
|
|
kv, _, _, _, router := newRunnerTestRouter()
|
|
user := &model.User{ID: "alice", Quota: model.DefaultQuota()}
|
|
kv.addKey("dpk_test", user, model.RoleUser)
|
|
|
|
rr := doRequest(router, http.MethodDelete, "/api/v1/runners/nonexistent", "", "dpk_test")
|
|
if rr.Code != http.StatusNotFound {
|
|
t.Fatalf("expected 404, got %d: %s", rr.Code, rr.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestDeleteRunner_ForbiddenOtherUser(t *testing.T) {
|
|
kv, us, rs, _, router := newRunnerTestRouter()
|
|
user := &model.User{ID: "alice", Quota: model.DefaultQuota()}
|
|
kv.addKey("dpk_test", user, model.RoleUser)
|
|
us.addUser(user)
|
|
|
|
rs.mu.Lock()
|
|
rs.runners["r1"] = &model.Runner{ID: "r1", User: "bob", Status: model.RunnerStatusReceived}
|
|
rs.mu.Unlock()
|
|
|
|
rr := doRequest(router, http.MethodDelete, "/api/v1/runners/r1", "", "dpk_test")
|
|
if rr.Code != http.StatusForbidden {
|
|
t.Fatalf("expected 403, got %d: %s", rr.Code, rr.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestUpdateRunnerStatus_Success(t *testing.T) {
|
|
kv, us, rs, _, router := newRunnerTestRouter()
|
|
user := &model.User{ID: "alice", Quota: model.DefaultQuota()}
|
|
kv.addKey("dpk_test", user, model.RoleUser)
|
|
us.addUser(user)
|
|
|
|
rs.mu.Lock()
|
|
rs.runners["r1"] = &model.Runner{ID: "r1", User: "alice", Status: model.RunnerStatusReceived}
|
|
rs.mu.Unlock()
|
|
|
|
body := `{"status":"pod_creating"}`
|
|
rr := doRequest(router, http.MethodPost, "/api/v1/runners/r1/status", body, "dpk_test")
|
|
if rr.Code != http.StatusOK {
|
|
t.Fatalf("expected 200, got %d: %s", rr.Code, rr.Body.String())
|
|
}
|
|
|
|
var runner model.Runner
|
|
decodeJSON(t, rr, &runner)
|
|
if runner.Status != model.RunnerStatusPodCreating {
|
|
t.Errorf("expected status pod_creating, got %s", runner.Status)
|
|
}
|
|
}
|
|
|
|
func TestUpdateRunnerStatus_InvalidTransition(t *testing.T) {
|
|
kv, us, rs, _, router := newRunnerTestRouter()
|
|
user := &model.User{ID: "alice", Quota: model.DefaultQuota()}
|
|
kv.addKey("dpk_test", user, model.RoleUser)
|
|
us.addUser(user)
|
|
|
|
rs.mu.Lock()
|
|
rs.runners["r1"] = &model.Runner{ID: "r1", User: "alice", Status: model.RunnerStatusReceived}
|
|
rs.mu.Unlock()
|
|
|
|
body := `{"status":"completed"}`
|
|
rr := doRequest(router, http.MethodPost, "/api/v1/runners/r1/status", body, "dpk_test")
|
|
if rr.Code != http.StatusBadRequest {
|
|
t.Fatalf("expected 400, got %d: %s", rr.Code, rr.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestUpdateRunnerStatus_NotFound(t *testing.T) {
|
|
kv, _, _, _, router := newRunnerTestRouter()
|
|
user := &model.User{ID: "alice", Quota: model.DefaultQuota()}
|
|
kv.addKey("dpk_test", user, model.RoleUser)
|
|
|
|
body := `{"status":"pod_creating"}`
|
|
rr := doRequest(router, http.MethodPost, "/api/v1/runners/nonexistent/status", body, "dpk_test")
|
|
if rr.Code != http.StatusNotFound {
|
|
t.Fatalf("expected 404, got %d: %s", rr.Code, rr.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestUpdateRunnerStatus_WithForgejoRunnerID(t *testing.T) {
|
|
kv, us, rs, _, router := newRunnerTestRouter()
|
|
user := &model.User{ID: "alice", Quota: model.DefaultQuota()}
|
|
kv.addKey("dpk_test", user, model.RoleUser)
|
|
us.addUser(user)
|
|
|
|
rs.mu.Lock()
|
|
rs.runners["r1"] = &model.Runner{ID: "r1", User: "alice", Status: model.RunnerStatusPodCreating}
|
|
rs.mu.Unlock()
|
|
|
|
body := `{"status":"runner_registered","forgejo_runner_id":"forgejo-99"}`
|
|
rr := doRequest(router, http.MethodPost, "/api/v1/runners/r1/status", body, "dpk_test")
|
|
if rr.Code != http.StatusOK {
|
|
t.Fatalf("expected 200, got %d: %s", rr.Code, rr.Body.String())
|
|
}
|
|
|
|
var runner model.Runner
|
|
decodeJSON(t, rr, &runner)
|
|
if runner.ForgejoRunnerID != "forgejo-99" {
|
|
t.Errorf("expected forgejo_runner_id forgejo-99, got %s", runner.ForgejoRunnerID)
|
|
}
|
|
}
|
|
|
|
func TestRunnerRoutes_NoAuth(t *testing.T) {
|
|
_, _, _, _, router := newRunnerTestRouter()
|
|
|
|
routes := []struct {
|
|
method string
|
|
path string
|
|
}{
|
|
{http.MethodPost, "/api/v1/runners"},
|
|
{http.MethodGet, "/api/v1/runners"},
|
|
{http.MethodDelete, "/api/v1/runners/r1"},
|
|
{http.MethodPost, "/api/v1/runners/r1/status"},
|
|
}
|
|
|
|
for _, rt := range routes {
|
|
t.Run(rt.method+" "+rt.path, func(t *testing.T) {
|
|
rr := doRequest(router, rt.method, rt.path, "", "")
|
|
if rr.Code != http.StatusUnauthorized {
|
|
t.Fatalf("expected 401, got %d", rr.Code)
|
|
}
|
|
})
|
|
}
|
|
}
|