Files
scrabble-game/backend/internal/inttest/admin_grant_test.go
T
Ilia Denisov d2d6955cbf
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 11s
CI / integration (pull_request) Successful in 26s
CI / ui (pull_request) Has been skipped
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m48s
feat(admin): admin grant — raw benefits and by-product reward bundles
The /_gm user card gains a Grant panel: grant raw benefit atoms (hints /
no-ads days / forever) or a defined value product (a reward bundle, including
an archived one), origin-picked. Both write an admin_grant ledger row via
payments.Grant / GrantProduct; the by-product grant records the source
product_id + snapshot. Both refuse a chips atom (never grant currency) or a
tournament atom (no credit target yet); chips/tournament products are also
kept out of the by-product picker.

Tests: the console grant end to end (raw, by-product, refuse a chips pack,
CSRF-guarded).
2026-07-10 05:35:55 +02:00

80 lines
3.1 KiB
Go

//go:build integration
package inttest
import (
"context"
"net/http"
"strings"
"testing"
"github.com/google/uuid"
"scrabble/backend/internal/payments"
)
// benefitFor returns the account's benefit on the given origin from its statement.
func benefitFor(t *testing.T, pay *payments.Service, id uuid.UUID, origin payments.Source) payments.OriginBenefit {
t.Helper()
stmt, err := pay.AccountStatement(context.Background(), id)
if err != nil {
t.Fatalf("statement: %v", err)
}
for _, b := range stmt.Benefits {
if b.Origin == origin {
return b
}
}
return payments.OriginBenefit{}
}
// TestConsoleAdminGrant drives the admin grant: a raw benefit grant, a by-product grant of a reward
// bundle, and a refusal to grant a chips pack; the create is CSRF-guarded.
func TestConsoleAdminGrant(t *testing.T) {
ctx := context.Background()
srv, _, pay := bannerServer(t)
h := srv.Handler()
id := provisionAccount(t)
const origin = "http://admin.test"
base := "http://admin.test/_gm/users/" + id.String()
// CSRF: a grant without the origin header is refused.
if code, _ := consoleDo(h, http.MethodPost, base+"/grant", "origin=direct&hints=1", ""); code != http.StatusForbidden {
t.Fatalf("grant without origin = %d, want 403", code)
}
// Raw grant: 5 hints + 30 no-ads days to the direct origin.
if code, body := consoleDo(h, http.MethodPost, base+"/grant", "origin=direct&hints=5&noads=30", origin); code != http.StatusOK || !strings.Contains(body, "Granted") {
t.Fatalf("raw grant = %d, has 'Granted' = %v", code, strings.Contains(body, "Granted"))
}
if b := benefitFor(t, pay, id, payments.SourceDirect); b.Hints != 5 || b.AdsPaidUntil.IsZero() {
t.Fatalf("after raw grant: hints=%d adsUntil-zero=%v, want 5 hints + a no-ads term", b.Hints, b.AdsPaidUntil.IsZero())
}
// By-product grant: an archived reward bundle (3 hints) to vk.
reward, err := pay.CreateProduct(ctx, payments.ProductInput{
Title: "reward-3-hints", Atoms: []payments.AtomLine{{Atom: "hints", Quantity: 3}},
}, false)
if err != nil {
t.Fatalf("create reward: %v", err)
}
if code, body := consoleDo(h, http.MethodPost, base+"/grant-product", "origin=vk&product_id="+reward.String(), origin); code != http.StatusOK || !strings.Contains(body, "Granted") {
t.Fatalf("product grant = %d, has 'Granted' = %v", code, strings.Contains(body, "Granted"))
}
if b := benefitFor(t, pay, id, payments.SourceVK); b.Hints != 3 {
t.Fatalf("after product grant: vk hints=%d, want 3", b.Hints)
}
// A chips pack cannot be granted.
pack, err := pay.CreateProduct(ctx, payments.ProductInput{
Title: "grant-pack", Atoms: []payments.AtomLine{{Atom: "chips", Quantity: 100}},
Prices: []payments.PriceLine{{Method: "direct", Currency: payments.CurrencyRUB, Amount: 14900}},
}, true)
if err != nil {
t.Fatalf("create pack: %v", err)
}
if code, body := consoleDo(h, http.MethodPost, base+"/grant-product", "origin=direct&product_id="+pack.String(), origin); code != http.StatusOK || !strings.Contains(body, "cannot grant chips") {
t.Fatalf("chips grant = %d, has 'cannot grant chips' = %v", code, strings.Contains(body, "cannot grant chips"))
}
}