Files
scrabble-game/backend/internal/server/middleware_console_test.go
T
Ilia Denisov aafdd46a4b
Tests · Go / test (push) Successful in 7s
Tests · Integration / integration (push) Successful in 11s
Tests · Go / test (pull_request) Successful in 6s
Tests · Integration / integration (pull_request) Successful in 13s
Stage 10: admin console & dictionary ops (complaint review, hot-reload, broadcasts)
Server-rendered admin console in the backend at /_gm (internal/adminconsole),
fronted on the gateway's public listener by Basic-Auth + a verbatim reverse proxy
(mounted on the edge mux below the h2c wrap). A same-origin check guards its POSTs;
no operator identity is tracked. This supersedes the Stage 6 gateway-fronts-
/api/v1/admin model: GATEWAY_ADMIN_ADDR and the backend /api/v1/admin ping are
dropped and gateway/internal/admin is repurposed to the verbatim proxy.

- Complaints: migration 00008 (+ jetgen) adds disposition/resolution_note/
  resolved_at/applied_in_version + the deferred status CHECK; resolution feeds a
  query-derived pending dictionary-change pipeline (marked applied after a reload).
- Dictionary hot-reload: per-version subdir BACKEND_DICT_DIR/<version>/ via the new
  Registry.LoadAvailable; engine.OpenWithVersions restores resident versions on
  restart. Partially addresses TODO-2.
- Broadcasts: a backend Telegram-connector client (internal/connector,
  BACKEND_CONNECTOR_ADDR) for SendToUser / SendToGameChannel (discharges the Stage 9
  forward-note).
- Admin reads: account.ListAccounts/CountAccounts/Identities and
  game.ListGames/CountGames/GameByID/ListComplaints/GetComplaint/CountComplaints/
  ResolveComplaint/DictionaryChanges/MarkChangesApplied.
- Tests: adminconsole render, engine reload, same-origin guard, gateway verbatim
  proxy + h2c console mount, inttest complaint pipeline + list/count + /_gm console.
- Docs: PLAN (Stage 10 done + refinements + TODO-2), ARCHITECTURE §1/§5/§6/§12/§13,
  FUNCTIONAL (+_ru), TESTING, backend/gateway READMEs.
2026-06-04 09:24:59 +02:00

52 lines
1.6 KiB
Go

package server
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
)
// TestSameOriginGuard checks the admin console's CSRF defence: safe methods pass,
// a state-changing request needs an Origin/Referer host matching the request Host.
func TestSameOriginGuard(t *testing.T) {
gin.SetMode(gin.TestMode)
e := gin.New()
g := e.Group("/_gm")
g.Use(requireSameOrigin())
g.POST("/act", func(c *gin.Context) { c.Status(http.StatusOK) })
g.GET("/page", func(c *gin.Context) { c.Status(http.StatusOK) })
cases := []struct {
name string
method string
path string
origin string
referer string
want int
}{
{"get is safe", http.MethodGet, "/_gm/page", "", "", http.StatusOK},
{"post without origin rejected", http.MethodPost, "/_gm/act", "", "", http.StatusForbidden},
{"post matching origin ok", http.MethodPost, "/_gm/act", "http://example.com", "", http.StatusOK},
{"post foreign origin rejected", http.MethodPost, "/_gm/act", "http://evil.test", "", http.StatusForbidden},
{"post matching referer ok", http.MethodPost, "/_gm/act", "", "http://example.com/_gm/x", http.StatusOK},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
req := httptest.NewRequest(tc.method, "http://example.com"+tc.path, nil)
if tc.origin != "" {
req.Header.Set("Origin", tc.origin)
}
if tc.referer != "" {
req.Header.Set("Referer", tc.referer)
}
rec := httptest.NewRecorder()
e.ServeHTTP(rec, req)
if rec.Code != tc.want {
t.Errorf("status = %d, want %d", rec.Code, tc.want)
}
})
}
}