e089a0a997
CI / changes (pull_request) Successful in 4s
CI / unit (pull_request) Successful in 11s
CI / integration (pull_request) Successful in 28s
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 1m46s
The /_gm user card gains a Finance panel: an account's chip balances per funding segment, benefits per origin (hints, no-ads until/forever), the recorded refund risk (abuse flag + floor-0 loss), and the append-only ledger history newest-first. Backed by a new payments.AccountStatement read straight from the materialized tables + the ledger (uncached — an admin, rare view). Folds the agreed admin / reports / catalog plan into PLAN.md (the PR stack: this panel, then the catalog editor, admin grant, refund + ledger export) and moves the tournament-entry storage design to the tournament stage.
102 lines
3.7 KiB
Go
102 lines
3.7 KiB
Go
//go:build integration
|
|
|
|
package inttest
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"scrabble/backend/internal/payments"
|
|
)
|
|
|
|
// TestAccountStatement checks the admin financial statement: a funded pack shows as a chip segment
|
|
// + a fund ledger row, an admin grant as a benefit + an admin_grant row, the ledger is newest-first,
|
|
// and a clean account carries no refund risk.
|
|
func TestAccountStatement(t *testing.T) {
|
|
ctx := context.Background()
|
|
svc := newPaymentsService()
|
|
acc := uuid.New()
|
|
|
|
// A funded pack: 100 chips to the direct segment + a fund ledger row.
|
|
prod := seedPackProduct(t, 100, methodPrice{method: "direct", currency: "RUB", amount: 14900})
|
|
res, err := svc.CreateOrder(ctx, acc, payments.NewContext("direct", "web"), []payments.Source{payments.SourceDirect}, prod, "robokassa")
|
|
if err != nil {
|
|
t.Fatalf("create order: %v", err)
|
|
}
|
|
paid, _ := payments.MoneyFromMinor(14900, payments.CurrencyRUB)
|
|
if _, err := svc.Fund(ctx, res.OrderID, "robokassa", res.OrderID.String(), paid); err != nil {
|
|
t.Fatalf("fund: %v", err)
|
|
}
|
|
// An admin grant: 5 hints to the direct origin + an admin_grant ledger row.
|
|
if err := svc.Grant(ctx, acc, payments.SourceDirect, 5, 0, false); err != nil {
|
|
t.Fatalf("grant: %v", err)
|
|
}
|
|
|
|
stmt, err := svc.AccountStatement(ctx, acc)
|
|
if err != nil {
|
|
t.Fatalf("statement: %v", err)
|
|
}
|
|
|
|
if len(stmt.Segments) != 1 || stmt.Segments[0].Source != payments.SourceDirect || stmt.Segments[0].Chips != 100 {
|
|
t.Fatalf("segments = %+v, want 100 chips on direct", stmt.Segments)
|
|
}
|
|
if len(stmt.Benefits) != 1 || stmt.Benefits[0].Origin != payments.SourceDirect || stmt.Benefits[0].Hints != 5 {
|
|
t.Fatalf("benefits = %+v, want 5 hints on direct", stmt.Benefits)
|
|
}
|
|
if len(stmt.Ledger) != 2 {
|
|
t.Fatalf("ledger rows = %d, want 2 (fund + admin_grant)", len(stmt.Ledger))
|
|
}
|
|
// Newest-first ordering (the grant is the later write).
|
|
if stmt.Ledger[0].CreatedAt.Before(stmt.Ledger[1].CreatedAt) {
|
|
t.Error("ledger is not newest-first")
|
|
}
|
|
kinds := map[string]int{}
|
|
for _, e := range stmt.Ledger {
|
|
kinds[e.Kind]++
|
|
if e.Kind == "fund" && e.ChipsDelta != 100 {
|
|
t.Errorf("fund chips delta = %d, want 100", e.ChipsDelta)
|
|
}
|
|
}
|
|
if kinds["fund"] != 1 || kinds["admin_grant"] != 1 {
|
|
t.Fatalf("ledger kinds = %v, want one fund + one admin_grant", kinds)
|
|
}
|
|
if stmt.Risk.Present {
|
|
t.Errorf("risk = %+v, want none for a clean account", stmt.Risk)
|
|
}
|
|
}
|
|
|
|
// TestConsoleFinancePanel checks the user card renders the finance panel: a funded pack + an admin
|
|
// grant surface as the segment balance, the benefit and the ledger rows.
|
|
func TestConsoleFinancePanel(t *testing.T) {
|
|
ctx := context.Background()
|
|
srv, _, pay := bannerServer(t)
|
|
id := provisionAccount(t)
|
|
|
|
prod := seedPackProduct(t, 100, methodPrice{method: "direct", currency: "RUB", amount: 14900})
|
|
res, err := pay.CreateOrder(ctx, id, payments.NewContext("direct", "web"), []payments.Source{payments.SourceDirect}, prod, "robokassa")
|
|
if err != nil {
|
|
t.Fatalf("create order: %v", err)
|
|
}
|
|
paid, _ := payments.MoneyFromMinor(14900, payments.CurrencyRUB)
|
|
if _, err := pay.Fund(ctx, res.OrderID, "robokassa", res.OrderID.String(), paid); err != nil {
|
|
t.Fatalf("fund: %v", err)
|
|
}
|
|
if err := pay.Grant(ctx, id, payments.SourceDirect, 5, 0, false); err != nil {
|
|
t.Fatalf("grant: %v", err)
|
|
}
|
|
|
|
code, body := consoleDo(srv.Handler(), http.MethodGet, "http://admin.test/_gm/users/"+id.String(), "", "")
|
|
if code != http.StatusOK {
|
|
t.Fatalf("user card = %d, want 200", code)
|
|
}
|
|
for _, want := range []string{"Finance", "Chips (direct)", "Benefits (direct)", "5 hints", "admin_grant", "fund"} {
|
|
if !strings.Contains(body, want) {
|
|
t.Errorf("finance panel missing %q", want)
|
|
}
|
|
}
|
|
}
|