matrixcloud / api /production_test.go
github-actions[bot]
Deploy from 4dfd54f2
857a91b
Raw
History Blame Contribute Delete
5.23 kB
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")
}
}