//go:build integration package inttest import ( "context" "net/http" "net/http/httptest" "net/url" "strings" "testing" "go.uber.org/zap/zaptest" "scrabble/backend/internal/account" "scrabble/backend/internal/game" "scrabble/backend/internal/server" ) // TestConsoleBlockAndUnblock drives the admin console block flow through HTTP: blocking a user // records the suspension and forfeits their active game, the user card renders both branches, // and unblocking lifts it. func TestConsoleBlockAndUnblock(t *testing.T) { ctx := context.Background() accounts := account.NewStore(testDB) games := newGameService() srv := consoleServer(t, accounts, games) gid, seats := newGameWithSeats(t, 2) blocked := seats[0] // The card renders the not-blocked branch before any block. if rec := consoleGet(t, srv, "/_gm/users/"+blocked.String()); rec.Code != http.StatusOK { t.Fatalf("user card before block = %d, want 200", rec.Code) } // Block permanently via the console. if rec := consolePost(t, srv, "/_gm/users/"+blocked.String()+"/block", url.Values{"duration": {"permanent"}}); rec.Code != http.StatusOK { t.Fatalf("console block = %d, want 200", rec.Code) } if _, ok, err := accounts.CurrentSuspension(ctx, blocked); err != nil || !ok { t.Fatalf("after console block: blocked=%v err=%v, want blocked", ok, err) } if g, err := games.GameByID(ctx, gid); err != nil || g.Status != game.StatusFinished { t.Fatalf("game after block: status=%q err=%v, want finished", g.Status, err) } // The card renders the blocked branch. if rec := consoleGet(t, srv, "/_gm/users/"+blocked.String()); rec.Code != http.StatusOK { t.Fatalf("user card after block = %d, want 200", rec.Code) } // Unblock via the console. if rec := consolePost(t, srv, "/_gm/users/"+blocked.String()+"/unblock", url.Values{}); rec.Code != http.StatusOK { t.Fatalf("console unblock = %d, want 200", rec.Code) } if _, ok, err := accounts.CurrentSuspension(ctx, blocked); err != nil || ok { t.Fatalf("after console unblock: blocked=%v err=%v, want not blocked", ok, err) } } // TestConsoleReasonsCRUD drives reason creation through the console and checks the page renders. func TestConsoleReasonsCRUD(t *testing.T) { ctx := context.Background() accounts := account.NewStore(testDB) srv := consoleServer(t, accounts, newGameService()) if rec := consoleGet(t, srv, "/_gm/reasons"); rec.Code != http.StatusOK { t.Fatalf("reasons page = %d, want 200", rec.Code) } form := url.Values{"text_en": {"Console reason"}, "text_ru": {"Причина из консоли"}} if rec := consolePost(t, srv, "/_gm/reasons", form); rec.Code != http.StatusOK { t.Fatalf("console create reason = %d, want 200", rec.Code) } reasons, err := accounts.ListReasons(ctx) if err != nil { t.Fatalf("list reasons: %v", err) } found := false for _, r := range reasons { if r.TextEn == "Console reason" && r.TextRu == "Причина из консоли" { found = true } } if !found { t.Error("reason created via the console was not found") } } // consoleServer assembles a server with the admin console mounted (it needs accounts, games and a // registry). func consoleServer(t *testing.T, accounts *account.Store, games *game.Service) *server.Server { t.Helper() return server.New(":0", server.Deps{ Logger: zaptest.NewLogger(t), DB: testDB, Accounts: accounts, Games: games, Registry: testRegistry, DictDir: dictDir(), }) } // consoleGet issues a console GET (a safe method, so the same-origin guard passes). func consoleGet(t *testing.T, srv *server.Server, path string) *httptest.ResponseRecorder { t.Helper() rec := httptest.NewRecorder() srv.Handler().ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "http://example.com"+path, nil)) return rec } // consolePost issues a same-origin form POST to the console. func consolePost(t *testing.T, srv *server.Server, path string, form url.Values) *httptest.ResponseRecorder { t.Helper() req := httptest.NewRequest(http.MethodPost, "http://example.com"+path, strings.NewReader(form.Encode())) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Origin", "http://example.com") rec := httptest.NewRecorder() srv.Handler().ServeHTTP(rec, req) return rec }