Files
Felis/internal/panel/panel_test.go
T
flyemoji 0c1cc598c1 feat(auth): migrate console login to passwordless
Replace console password auth with a passwordless surface — the pre-session
login doors plus an identifier-first discovery endpoint — and remove the
password paths.

- Login doors (Public, pre-session): email-OTP, passkey assertion, op.console
  login with in-game approval, and setup-token redeem.
- /api/v1/auth/options: identifier-first discovery reporting which console
  methods an email can use. The single sanctioned existence oracle; methods
  are computed with no role branch, so staff and player accounts in the same
  credential state return byte-identical bodies (staffness invisible by
  construction).
- Remove password auth: drop StaffUser.PasswordHash and the /auth/login,
  /auth/change-password and /users/{id}/reset-password endpoints (and test).
- Data layer: UserByEmail, verified-email uniqueness, setup-token store
  (migration 0012).
- Reconcile docs/openapi.yaml with the served surface; the method/path/face/
  tier parity gate (TestOpenAPIMatchesServedRoutes) passes.
- felis TUI: in-game MC bind, owner/break-glass OP provisioning, version.
- Velocity /felis command suite.

Consolidates the accumulated backend migration work; the frontend (panel/)
is left untouched. Full Go tree green on WSL (go build ./... && go test ./...).
2026-07-04 21:47:12 +09:00

60 lines
2.0 KiB
Go

package panel
import (
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func TestHandlerServesPanelAndConfig(t *testing.T) {
api := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/api/v1/me" {
t.Fatalf("api saw unexpected path %q", r.URL.Path)
}
w.WriteHeader(http.StatusTeapot)
})
h := Handler(api, "example.test", "console.example.test", "op.console.example.test", "v1.2.3")
w := httptest.NewRecorder()
h.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/", nil))
if w.Code != http.StatusOK || !strings.Contains(w.Body.String(), "Felis") {
t.Fatalf("index response = %d %q", w.Code, w.Body.String())
}
w = httptest.NewRecorder()
h.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/servers/survival", nil))
if w.Code != http.StatusOK || !strings.Contains(w.Body.String(), "Felis") {
t.Fatalf("spa fallback = %d %q", w.Code, w.Body.String())
}
w = httptest.NewRecorder()
h.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/config.json", nil))
if w.Code != http.StatusOK {
t.Fatalf("config status = %d", w.Code)
}
var cfg runtimeConfig
if err := json.Unmarshal(w.Body.Bytes(), &cfg); err != nil {
t.Fatalf("decode config: %v", err)
}
if cfg.APIBase != "/api/v1" || cfg.RootDomain != "example.test" {
t.Fatalf("config = %+v", cfg)
}
// The tiering plumbing surfaces the two console hostnames so one bundle can
// detect which face it is being served from, plus a resolved build stamp for
// the version badge. A clean release tag resolves to a non-dev build.
if cfg.PanelHostname != "console.example.test" || cfg.AdminHostname != "op.console.example.test" {
t.Fatalf("config tiering hostnames = %+v", cfg)
}
if cfg.Build.Version != "v1.2.3" || cfg.Build.Release != "v1.2.3" || cfg.Build.Dev {
t.Fatalf("config build stamp = %+v", cfg.Build)
}
w = httptest.NewRecorder()
h.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/api/v1/me", nil))
if w.Code != http.StatusTeapot {
t.Fatalf("api status = %d", w.Code)
}
}