115 lines
4.0 KiB
Go
115 lines
4.0 KiB
Go
package routes
|
|
|
|
import (
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"nearle/facade"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
)
|
|
|
|
// Can this server be built and can its routes be reached?
|
|
//
|
|
// Everything else in this repository tests a function. This tests the thing
|
|
// that actually happens on deploy: the whole object graph is constructed and
|
|
// every route is registered. Nothing here needs a database — the repositories
|
|
// hold their handle without touching it — so it runs in CI beside the unit
|
|
// tests rather than in an environment somebody has to provision.
|
|
//
|
|
// ── Why it is worth its own file ────────────────────────────────────────────
|
|
//
|
|
// Three things in `NewFacade` PANIC rather than return an error: a tool that
|
|
// fails to register, a help corpus that will not load, and an agent naming a
|
|
// tool that does not exist. Each is a programming mistake that should stop a
|
|
// deploy, and each was previously reachable only by starting the server against
|
|
// a real database — which meant, in practice, by deploying.
|
|
//
|
|
// The agent one is not hypothetical: a typo in `agents/orders.yaml` is a file
|
|
// edit away, and it takes a working assistant down at boot.
|
|
|
|
func testFacade(t *testing.T) *facade.Facade {
|
|
t.Helper()
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
// Rendered as a failure rather than a panicking test, because the
|
|
// message IS the point — "agent orders lists a tool that does not
|
|
// exist" is the whole diagnosis.
|
|
t.Fatalf("the server cannot start: %v", r)
|
|
}
|
|
}()
|
|
// No database, no catalogue, no embedder, no model. A deployment with none
|
|
// of those must still boot and say what it is missing, rather than failing
|
|
// somewhere the operator cannot see.
|
|
return facade.NewFacade(nil, nil, nil, nil, "", "no model in tests")
|
|
}
|
|
|
|
func TestTheServerCanBeBuilt(t *testing.T) {
|
|
f := testFacade(t)
|
|
if f == nil {
|
|
t.Fatal("no facade")
|
|
}
|
|
// The two doors onto the assistant. Absent means a route registered below
|
|
// would nil-panic on its first request rather than at boot.
|
|
if f.AssistantController == nil {
|
|
t.Fatal("no assistant controller")
|
|
}
|
|
if f.MCPController == nil {
|
|
t.Fatal("no MCP controller")
|
|
}
|
|
if f.Tools == nil {
|
|
t.Fatal("no tool registry")
|
|
}
|
|
}
|
|
|
|
func TestEveryAssistantRouteIsReachable(t *testing.T) {
|
|
// Registered, not merely written down. A route added to a file that nothing
|
|
// calls is invisible until somebody reports the feature missing.
|
|
app := fiber.New()
|
|
RegisterRoutes(app, testFacade(t))
|
|
|
|
for _, route := range []struct {
|
|
method, path string
|
|
}{
|
|
{"GET", "/live/api/v1/web/assistant/status"},
|
|
{"POST", "/live/api/v1/web/assistant/ask"},
|
|
{"POST", "/live/api/v1/web/assistant/approve"},
|
|
{"POST", "/live/api/v1/web/assistant/mcp"},
|
|
} {
|
|
req := httptest.NewRequest(route.method, route.path, strings.NewReader("{}"))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := app.Test(req, -1)
|
|
if err != nil {
|
|
t.Fatalf("%s %s: %v", route.method, route.path, err)
|
|
}
|
|
if resp.StatusCode == fiber.StatusNotFound {
|
|
t.Fatalf("%s %s is not registered", route.method, route.path)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTheAssistantSurfaceSitsBehindTheSessionGuard(t *testing.T) {
|
|
// The assistant reads the same data the console does and must read it as
|
|
// the same person. Being under `/v1/web` is what puts it behind WebAuth —
|
|
// a route registered one path segment to the left would answer anybody.
|
|
app := fiber.New()
|
|
RegisterRoutes(app, testFacade(t))
|
|
|
|
// WEB_AUTH_REQUIRED is off by default, so an untokened request reaches the
|
|
// handler; the assistant's own controller then refuses it. Either way it
|
|
// must not answer with data.
|
|
req := httptest.NewRequest("POST", "/live/api/v1/web/assistant/ask",
|
|
strings.NewReader(`{"question":"what is stuck?"}`))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := app.Test(req, -1)
|
|
if err != nil {
|
|
t.Fatalf("calling: %v", err)
|
|
}
|
|
if resp.StatusCode == fiber.StatusOK {
|
|
t.Fatal("an untokened question was answered")
|
|
}
|
|
}
|