8881214213
Mechanical, behaviour-preserving removal of Stage N / TODO-N / phase (RN) references from comments, doc-comments, service READMEs, the current-state docs (ARCHITECTURE, FUNCTIONAL+_ru, TESTING, UI_DESIGN), config-file comments, and the .fbs/.proto schema comments. PLAN.md / PRERELEASE.md / CLAUDE.md keep the stage history. - Rename the only stage-named identifiers: registerStage8 -> registerSocialOps, registerStage11 -> registerLinkOps (gateway transcode). - Split stage6_test.go: TestEmailLoginFlow -> email_test.go, TestGuestAutoMatchLeavesNoStats (+ provisionGuest) -> account_test.go. - Regenerated proto bindings (push.pb.go, telegram_grpc.pb.go) from the de-staged .proto comments; FB Go/TS bindings unchanged (flatc strips schema comments). go build/vet/gofmt clean across modules; integration typecheck and pnpm check green.
200 lines
7.0 KiB
Go
200 lines
7.0 KiB
Go
package transcode_test
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"testing"
|
|
|
|
flatbuffers "github.com/google/flatbuffers/go"
|
|
|
|
"scrabble/gateway/internal/transcode"
|
|
fb "scrabble/pkg/fbs/scrabblefb"
|
|
)
|
|
|
|
// TestGameStateIncludesAlphabet checks the include_alphabet flag reaches the backend and
|
|
// the returned alphabet table plus the index rack (a blank is 255) are encoded into the
|
|
// StateView.
|
|
func TestGameStateIncludesAlphabet(t *testing.T) {
|
|
backend, cleanup := fakeBackend(t, func(w http.ResponseWriter, r *http.Request) {
|
|
if got := r.URL.Query().Get("include_alphabet"); got != "true" {
|
|
t.Errorf("include_alphabet query = %q, want true", got)
|
|
}
|
|
_, _ = w.Write([]byte(`{"game":{"id":"g-1","variant":"scrabble_en","status":"active","players":2,"to_move":0,"seats":[]},"seat":0,"rack":[0,255],"bag_len":50,"hints_remaining":0,"alphabet":[{"index":0,"letter":"a","value":1},{"index":1,"letter":"b","value":3}]}`))
|
|
})
|
|
defer cleanup()
|
|
|
|
reg := transcode.NewRegistry(backend, nil)
|
|
op, _ := reg.Lookup(transcode.MsgGameState)
|
|
|
|
b := flatbuffers.NewBuilder(32)
|
|
gid := b.CreateString("g-1")
|
|
fb.StateRequestStart(b)
|
|
fb.StateRequestAddGameId(b, gid)
|
|
fb.StateRequestAddIncludeAlphabet(b, true)
|
|
b.Finish(fb.StateRequestEnd(b))
|
|
|
|
payload, err := op.Handler(context.Background(), transcode.Request{Payload: b.FinishedBytes(), UserID: "u-1"})
|
|
if err != nil {
|
|
t.Fatalf("handler: %v", err)
|
|
}
|
|
st := fb.GetRootAsStateView(payload, 0)
|
|
if st.RackLength() != 2 || st.Rack(0) != 0 || st.Rack(1) != 255 {
|
|
t.Fatalf("rack indices wrong: len=%d [0]=%d [1]=%d", st.RackLength(), st.Rack(0), st.Rack(1))
|
|
}
|
|
if st.AlphabetLength() != 2 {
|
|
t.Fatalf("alphabet length = %d, want 2", st.AlphabetLength())
|
|
}
|
|
var e fb.AlphabetEntry
|
|
st.Alphabet(&e, 0)
|
|
if e.Index() != 0 || string(e.Letter()) != "a" || e.Value() != 1 {
|
|
t.Errorf("alphabet[0] = %d/%q/%d, want 0/a/1", e.Index(), e.Letter(), e.Value())
|
|
}
|
|
}
|
|
|
|
// TestGameStateOmitsAlphabetByDefault checks the table is neither requested nor encoded on
|
|
// the steady-state poll (no include_alphabet flag).
|
|
func TestGameStateOmitsAlphabetByDefault(t *testing.T) {
|
|
backend, cleanup := fakeBackend(t, func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Query().Get("include_alphabet") == "true" {
|
|
t.Error("include_alphabet should be unset")
|
|
}
|
|
_, _ = w.Write([]byte(`{"game":{"id":"g-1","variant":"scrabble_en","status":"active","players":2,"to_move":0,"seats":[]},"seat":0,"rack":[2,0,19],"bag_len":50,"hints_remaining":0}`))
|
|
})
|
|
defer cleanup()
|
|
|
|
reg := transcode.NewRegistry(backend, nil)
|
|
op, _ := reg.Lookup(transcode.MsgGameState)
|
|
b := flatbuffers.NewBuilder(32)
|
|
gid := b.CreateString("g-1")
|
|
fb.StateRequestStart(b)
|
|
fb.StateRequestAddGameId(b, gid)
|
|
b.Finish(fb.StateRequestEnd(b))
|
|
payload, err := op.Handler(context.Background(), transcode.Request{Payload: b.FinishedBytes(), UserID: "u-1"})
|
|
if err != nil {
|
|
t.Fatalf("handler: %v", err)
|
|
}
|
|
st := fb.GetRootAsStateView(payload, 0)
|
|
if st.AlphabetLength() != 0 {
|
|
t.Errorf("alphabet length = %d, want 0", st.AlphabetLength())
|
|
}
|
|
if st.RackLength() != 3 {
|
|
t.Errorf("rack length = %d, want 3", st.RackLength())
|
|
}
|
|
}
|
|
|
|
// TestSubmitPlayForwardsIndexTiles checks PlayTile indices reach the backend as integer
|
|
// letter fields in the JSON body, blank flag preserved.
|
|
func TestSubmitPlayForwardsIndexTiles(t *testing.T) {
|
|
var body struct {
|
|
Dir string `json:"dir"`
|
|
Tiles []struct {
|
|
Row int `json:"row"`
|
|
Col int `json:"col"`
|
|
Letter int `json:"letter"`
|
|
Blank bool `json:"blank"`
|
|
} `json:"tiles"`
|
|
}
|
|
backend, cleanup := fakeBackend(t, func(w http.ResponseWriter, r *http.Request) {
|
|
raw, _ := io.ReadAll(r.Body)
|
|
if err := json.Unmarshal(raw, &body); err != nil {
|
|
t.Fatalf("decode body: %v", err)
|
|
}
|
|
_, _ = w.Write([]byte(`{"move":{"player":0,"action":"play","words":["CAT"],"score":9},"game":{"id":"g-5","status":"active","seats":[]}}`))
|
|
})
|
|
defer cleanup()
|
|
|
|
reg := transcode.NewRegistry(backend, nil)
|
|
op, _ := reg.Lookup(transcode.MsgGameSubmitPlay)
|
|
|
|
b := flatbuffers.NewBuilder(64)
|
|
gid := b.CreateString("g-5")
|
|
dir := b.CreateString("H")
|
|
fb.PlayTileStart(b)
|
|
fb.PlayTileAddRow(b, 7)
|
|
fb.PlayTileAddCol(b, 7)
|
|
fb.PlayTileAddLetter(b, 2)
|
|
fb.PlayTileAddBlank(b, true)
|
|
tile := fb.PlayTileEnd(b)
|
|
fb.SubmitPlayRequestStartTilesVector(b, 1)
|
|
b.PrependUOffsetT(tile)
|
|
tiles := b.EndVector(1)
|
|
fb.SubmitPlayRequestStart(b)
|
|
fb.SubmitPlayRequestAddGameId(b, gid)
|
|
fb.SubmitPlayRequestAddDir(b, dir)
|
|
fb.SubmitPlayRequestAddTiles(b, tiles)
|
|
b.Finish(fb.SubmitPlayRequestEnd(b))
|
|
|
|
if _, err := op.Handler(context.Background(), transcode.Request{Payload: b.FinishedBytes(), UserID: "u-1"}); err != nil {
|
|
t.Fatalf("handler: %v", err)
|
|
}
|
|
if len(body.Tiles) != 1 || body.Tiles[0].Letter != 2 || !body.Tiles[0].Blank || body.Tiles[0].Row != 7 {
|
|
t.Fatalf("forwarded tiles wrong: %+v", body.Tiles)
|
|
}
|
|
}
|
|
|
|
// TestCheckWordForwardsIndices checks the word-check query rides as repeated ?idx= params
|
|
// and the decoded concrete word echoes back.
|
|
func TestCheckWordForwardsIndices(t *testing.T) {
|
|
backend, cleanup := fakeBackend(t, func(w http.ResponseWriter, r *http.Request) {
|
|
if got := r.URL.Query()["idx"]; len(got) != 3 || got[0] != "2" || got[2] != "19" {
|
|
t.Errorf("idx params = %v, want [2 0 19]", got)
|
|
}
|
|
_, _ = w.Write([]byte(`{"word":"cat","legal":true}`))
|
|
})
|
|
defer cleanup()
|
|
|
|
reg := transcode.NewRegistry(backend, nil)
|
|
op, _ := reg.Lookup(transcode.MsgGameCheckWord)
|
|
|
|
b := flatbuffers.NewBuilder(32)
|
|
gid := b.CreateString("g-1")
|
|
word := b.CreateByteVector([]byte{2, 0, 19})
|
|
fb.CheckWordRequestStart(b)
|
|
fb.CheckWordRequestAddGameId(b, gid)
|
|
fb.CheckWordRequestAddWord(b, word)
|
|
b.Finish(fb.CheckWordRequestEnd(b))
|
|
|
|
payload, err := op.Handler(context.Background(), transcode.Request{Payload: b.FinishedBytes(), UserID: "u-1"})
|
|
if err != nil {
|
|
t.Fatalf("handler: %v", err)
|
|
}
|
|
res := fb.GetRootAsWordCheckResult(payload, 0)
|
|
if string(res.Word()) != "cat" || !res.Legal() {
|
|
t.Errorf("word check = %q/%v, want cat/true", res.Word(), res.Legal())
|
|
}
|
|
}
|
|
|
|
// TestExchangeForwardsIndices checks rack-exchange indices (blank 255) reach the backend
|
|
// body.
|
|
func TestExchangeForwardsIndices(t *testing.T) {
|
|
var body struct {
|
|
Tiles []int `json:"tiles"`
|
|
}
|
|
backend, cleanup := fakeBackend(t, func(w http.ResponseWriter, r *http.Request) {
|
|
raw, _ := io.ReadAll(r.Body)
|
|
_ = json.Unmarshal(raw, &body)
|
|
_, _ = w.Write([]byte(`{"move":{"player":0,"action":"exchange","count":2},"game":{"id":"g-1","status":"active","seats":[]}}`))
|
|
})
|
|
defer cleanup()
|
|
|
|
reg := transcode.NewRegistry(backend, nil)
|
|
op, _ := reg.Lookup(transcode.MsgGameExchange)
|
|
|
|
b := flatbuffers.NewBuilder(32)
|
|
gid := b.CreateString("g-1")
|
|
tiles := b.CreateByteVector([]byte{0, 255})
|
|
fb.ExchangeRequestStart(b)
|
|
fb.ExchangeRequestAddGameId(b, gid)
|
|
fb.ExchangeRequestAddTiles(b, tiles)
|
|
b.Finish(fb.ExchangeRequestEnd(b))
|
|
|
|
if _, err := op.Handler(context.Background(), transcode.Request{Payload: b.FinishedBytes(), UserID: "u-1"}); err != nil {
|
|
t.Fatalf("handler: %v", err)
|
|
}
|
|
if len(body.Tiles) != 2 || body.Tiles[0] != 0 || body.Tiles[1] != 255 {
|
|
t.Errorf("forwarded exchange tiles = %v, want [0 255]", body.Tiles)
|
|
}
|
|
}
|