package transcode_test import ( "context" "encoding/json" "net/http" "net/url" "testing" flatbuffers "github.com/google/flatbuffers/go" "scrabble/gateway/internal/transcode" fb "scrabble/pkg/fbs/scrabblefb" ) const ( vkTestSecret = "wv527nm6mvf3rgomfhd6" // vkGoldenSign is the base64url HMAC-SHA256 of the unsigned params below under // vkTestSecret, computed independently (a Python reference) — so a green test // exercises VK's real algorithm end to end, not a self-consistent fake. vkGoldenSign = "x7RUe9sKN05Bigm-pBIl8gLmfMwZCloPrwZaZEQAdOA" ) // vkParams is the canonical VK launch-parameter set (without the sign) that // vkGoldenSign covers. func vkParams() url.Values { return url.Values{ "vk_access_token_settings": {""}, "vk_app_id": {"6736218"}, "vk_are_notifications_enabled": {"0"}, "vk_is_app_user": {"1"}, "vk_is_favorite": {"0"}, "vk_language": {"ru"}, "vk_platform": {"android"}, "vk_ref": {"other"}, "vk_ts": {"1546961916"}, "vk_user_id": {"494075"}, } } func vkLoginPayload(params, browserTz, displayName string) []byte { b := flatbuffers.NewBuilder(0) p := b.CreateString(params) tz := b.CreateString(browserTz) dn := b.CreateString(displayName) fb.VKLoginRequestStart(b) fb.VKLoginRequestAddParams(b, p) fb.VKLoginRequestAddBrowserTz(b, tz) fb.VKLoginRequestAddDisplayName(b, dn) b.Finish(fb.VKLoginRequestEnd(b)) return b.FinishedBytes() } func TestVKAuthForwardsSeedFields(t *testing.T) { var gotBody map[string]string backend, cleanup := fakeBackend(t, func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/api/v1/internal/sessions/vk" { t.Errorf("unexpected path %q", r.URL.Path) } _ = json.NewDecoder(r.Body).Decode(&gotBody) _, _ = w.Write([]byte(`{"token":"tok-vk","user_id":"u-vk","is_guest":false,"display_name":"Иван"}`)) }) defer cleanup() reg := transcode.NewRegistry(backend, nil, transcode.WithVKAuth(vkTestSecret)) op, ok := reg.Lookup(transcode.MsgAuthVK) if !ok { t.Fatal("auth.vk not registered") } signed := vkParams() signed.Set("sign", vkGoldenSign) payload, err := op.Handler(context.Background(), transcode.Request{Payload: vkLoginPayload(signed.Encode(), "+03:00", "Иван Петров")}) if err != nil { t.Fatalf("handler: %v", err) } sess := fb.GetRootAsSession(payload, 0) if string(sess.Token()) != "tok-vk" || string(sess.UserId()) != "u-vk" { t.Fatalf("session decoded wrong: token=%q user=%q", sess.Token(), sess.UserId()) } // The verified vk_user_id and vk_language plus the client-supplied display name are // forwarded so the backend can seed a brand-new account. if gotBody["external_id"] != "494075" || gotBody["language_code"] != "ru" || gotBody["display_name"] != "Иван Петров" { t.Errorf("forwarded body = %+v, want external_id=494075 language_code=ru display_name=Иван Петров", gotBody) } } // TestVKAuthInvalidSign confirms a bad signature is a domain failure (invalid_vk_params) // and the backend is never called. func TestVKAuthInvalidSign(t *testing.T) { backend, cleanup := fakeBackend(t, func(http.ResponseWriter, *http.Request) { t.Error("backend must not be called when the sign is invalid") }) defer cleanup() reg := transcode.NewRegistry(backend, nil, transcode.WithVKAuth(vkTestSecret)) op, _ := reg.Lookup(transcode.MsgAuthVK) tampered := vkParams() tampered.Set("sign", "deadbeef") _, err := op.Handler(context.Background(), transcode.Request{Payload: vkLoginPayload(tampered.Encode(), "", "")}) if code, ok := transcode.DomainCode(err); !ok || code != "invalid_vk_params" { t.Errorf("DomainCode = (%q, %v), want (invalid_vk_params, true)", code, ok) } } // TestVKAuthDisabledWithoutSecret confirms a blank VK app secret leaves auth.vk // unregistered. func TestVKAuthDisabledWithoutSecret(t *testing.T) { backend, cleanup := fakeBackend(t, func(http.ResponseWriter, *http.Request) {}) defer cleanup() reg := transcode.NewRegistry(backend, nil) if _, ok := reg.Lookup(transcode.MsgAuthVK); ok { t.Error("auth.vk should be unregistered without a VK app secret") } }