8700fbfae1
- backend + gateway multi-stage distroless Dockerfiles; the gateway embeds and
serves the SPA at / and /telegram/ via go:embed (committed dist placeholder,
real build baked in by the image's node stage)
- deploy/docker-compose.yml: backend + gateway + Postgres + Telegram connector
(VPN sidecar) + OTel Collector + Prometheus (15d) + Tempo (72h) + Grafana,
fronted by a caddy owning a single /_gm Basic-Auth (admin console + Grafana
subpath); inter-service on a private network, only caddy on the edge network
- new metrics: backend accounts_created_total{kind} (robots excluded) and an
in-memory gateway active_users{window=24h,7d} gauge
- CI: single .gitea/workflows/ci.yaml (unit/integration/ui + a gated test-contour
deploy) on the new feature/* -> development -> master branch model; the old
go-unit/integration/ui-test workflows are folded in; the connector-scoped
compose is retired (superseded by deploy/)
- docs: ARCHITECTURE §11/§12/§13, root + gateway READMEs, CLAUDE.md branching,
PLAN.md (stage 16 done + refinements + Stage 17 forward-notes)
53 lines
1.6 KiB
Go
53 lines
1.6 KiB
Go
package webui
|
|
|
|
import (
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// get drives the handler with a GET for the given path and returns the response.
|
|
func get(t *testing.T, h http.Handler, target string) *http.Response {
|
|
t.Helper()
|
|
rec := httptest.NewRecorder()
|
|
h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, target, nil))
|
|
return rec.Result()
|
|
}
|
|
|
|
func TestHandlerServesIndexAndFallsBack(t *testing.T) {
|
|
h := Handler("")
|
|
|
|
// The embedded placeholder index is served at the root.
|
|
if resp := get(t, h, "/"); resp.StatusCode != http.StatusOK {
|
|
t.Fatalf("GET / status = %d, want 200", resp.StatusCode)
|
|
}
|
|
|
|
// An existing (non-index) file is served directly by the file server.
|
|
if resp := get(t, h, "/assets/.gitkeep"); resp.StatusCode != http.StatusOK {
|
|
t.Fatalf("GET /assets/.gitkeep status = %d, want 200 (served file)", resp.StatusCode)
|
|
}
|
|
|
|
// An unknown deep link falls back to the SPA shell (200, not 404) so the
|
|
// client-side hash router can take over.
|
|
resp := get(t, h, "/game/abc/deep")
|
|
if resp.StatusCode != http.StatusOK {
|
|
t.Fatalf("GET /game/abc/deep status = %d, want 200 (SPA fallback)", resp.StatusCode)
|
|
}
|
|
body, _ := io.ReadAll(resp.Body)
|
|
if !strings.Contains(string(body), "<html") {
|
|
t.Fatalf("fallback body is not the index HTML: %q", body)
|
|
}
|
|
}
|
|
|
|
func TestHandlerStripsPrefix(t *testing.T) {
|
|
h := Handler("/telegram/")
|
|
|
|
for _, target := range []string{"/telegram/", "/telegram/assets/.gitkeep", "/telegram/lobby/x"} {
|
|
if resp := get(t, h, target); resp.StatusCode != http.StatusOK {
|
|
t.Fatalf("GET %s status = %d, want 200", target, resp.StatusCode)
|
|
}
|
|
}
|
|
}
|