Spaces:
Runtime error
Runtime error
File size: 5,234 Bytes
857a91b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 | package api
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/agent-matrix/matrix-runtime/internal/config"
"github.com/agent-matrix/matrix-runtime/internal/jobs"
)
func get(t *testing.T, srv *Server, path, token string) (*httptest.ResponseRecorder, map[string]any) {
t.Helper()
req := httptest.NewRequest(http.MethodGet, path, nil)
if token != "" {
req.Header.Set("Authorization", "Bearer "+token)
}
rec := httptest.NewRecorder()
srv.Handler().ServeHTTP(rec, req)
var out map[string]any
_ = json.Unmarshal(rec.Body.Bytes(), &out)
return rec, out
}
func TestVersionAndReady(t *testing.T) {
srv := testServer(t)
rec, body := get(t, srv, "/v1/version", "")
if rec.Code != http.StatusOK || body["name"] != "matrix-runtime" {
t.Fatalf("version = %d %v", rec.Code, body)
}
rec, body = get(t, srv, "/v1/ready", "")
// No store in testServer → not ready, with a store_unavailable warning.
if rec.Code != http.StatusServiceUnavailable {
t.Fatalf("ready status = %d, want 503 (no store)", rec.Code)
}
if _, ok := body["checks"].(map[string]any); !ok {
t.Fatalf("ready missing checks: %v", body)
}
if body["ready"] != false {
t.Errorf("ready = %v, want false", body["ready"])
}
}
func TestReadyHealthyWithStore(t *testing.T) {
srv := authServer(t) // has a store + temp data dir, local-dev
rec, body := get(t, srv, "/v1/ready", "")
if rec.Code != http.StatusOK || body["ready"] != true {
t.Fatalf("ready = %d %v", rec.Code, body)
}
// local-dev should surface a local_dev_mode warning.
ws, _ := body["warnings"].([]any)
found := false
for _, w := range ws {
if m, ok := w.(map[string]any); ok && m["code"] == "local_dev_mode" {
found = true
}
}
if !found {
t.Errorf("expected local_dev_mode warning, got %v", ws)
}
}
func TestProductionFailsClosedWithoutCreds(t *testing.T) {
cfg := config.Defaults(config.ModeCustomerAgent) // production mode
cfg.DataDir = t.TempDir()
srv := NewServer(cfg, jobs.NewManager(cfg), nil)
// Probes stay public.
if rec, _ := get(t, srv, "/v1/health", ""); rec.Code != http.StatusOK {
t.Fatalf("health should be public, got %d", rec.Code)
}
if rec, _ := get(t, srv, "/v1/version", ""); rec.Code != http.StatusOK {
t.Fatalf("version should be public, got %d", rec.Code)
}
// A protected endpoint with no creds is rejected (fail-closed).
if rec, _ := get(t, srv, "/v1/jobs", ""); rec.Code != http.StatusUnauthorized {
t.Fatalf("protected endpoint = %d, want 401", rec.Code)
}
}
func TestOperatorAPITokenGate(t *testing.T) {
cfg := config.Defaults(config.ModeCustomerAgent)
cfg.DataDir = t.TempDir()
cfg.APIToken = "s3cret-operator-token"
srv := NewServer(cfg, jobs.NewManager(cfg), nil)
if rec, _ := get(t, srv, "/v1/jobs", ""); rec.Code != http.StatusUnauthorized {
t.Fatalf("no token = %d, want 401", rec.Code)
}
if rec, _ := get(t, srv, "/v1/jobs", "wrong"); rec.Code != http.StatusUnauthorized {
t.Fatalf("wrong token = %d, want 401", rec.Code)
}
if rec, _ := get(t, srv, "/v1/jobs", "s3cret-operator-token"); rec.Code == http.StatusUnauthorized {
t.Fatalf("correct operator token should pass the auth gate, got 401")
}
}
func TestMatrixShellGate(t *testing.T) {
// Production mode → MatrixShell disabled by default → 403.
cfg := config.Defaults(config.ModeCustomerAgent)
cfg.DataDir = t.TempDir()
cfg.APIToken = "tok"
srv := NewServer(cfg, jobs.NewManager(cfg), nil)
if rec, _ := get(t, srv, "/v1/matrixshell/status", "tok"); rec.Code != http.StatusForbidden {
t.Fatalf("matrixshell (prod, disabled) = %d, want 403", rec.Code)
}
// Explicitly enabled → reachable (200).
cfg2 := config.Defaults(config.ModeCustomerAgent)
cfg2.DataDir = t.TempDir()
cfg2.APIToken = "tok"
cfg2.MatrixShellEnabled = true
srv2 := NewServer(cfg2, jobs.NewManager(cfg2), nil)
if rec, _ := get(t, srv2, "/v1/matrixshell/status", "tok"); rec.Code == http.StatusForbidden {
t.Fatalf("matrixshell (enabled) should not be 403")
}
}
func TestStorageEndpoint(t *testing.T) {
srv := authServer(t) // has store + temp data dir, local-dev (no auth required)
rec, body := get(t, srv, "/v1/system/storage", "")
if rec.Code != http.StatusOK {
t.Fatalf("storage = %d", rec.Code)
}
if _, ok := body["areas"].(map[string]any); !ok {
t.Fatalf("missing areas: %v", body)
}
if _, ok := body["total_bytes"]; !ok {
t.Errorf("missing total_bytes")
}
}
func TestRateLimit(t *testing.T) {
cfg := config.Defaults(config.ModeLocalDev)
cfg.DataDir = t.TempDir()
cfg.RateLimitRPM = 3
srv := NewServer(cfg, jobs.NewManager(cfg), nil)
// GET probes are never limited.
for i := 0; i < 10; i++ {
if rec, _ := get(t, srv, "/v1/health", ""); rec.Code == http.StatusTooManyRequests {
t.Fatalf("GET health should never be rate limited")
}
}
// POSTs are limited after RateLimitRPM within the window.
limited := false
for i := 0; i < 6; i++ {
req := httptest.NewRequest(http.MethodPost, "/v1/auth/login", jsonBody(`{"email":"a@b.io","password":"x"}`))
rec := httptest.NewRecorder()
srv.Handler().ServeHTTP(rec, req)
if rec.Code == http.StatusTooManyRequests {
limited = true
break
}
}
if !limited {
t.Errorf("expected a 429 after exceeding the rate limit")
}
}
|