feat(enakgame): spin as an EnakGame game; remove the old game flow
EnakGame phase 10 of docs/tasks-enakgame.md (EG-1001 to EG-1003). Spin (EG-1001) - PROBABILITY entries take an optional label (a wheel segment). The customer game list shows a PROBABILITY game's prizes (entry, label, amount, never weights), and completing returns the drawn prize, so the client can draw the wheel and stop it on the server's draw. - docs/enakgame-spin.md: the admin steps to set up spin per organization (no seeder) and the customer app flow. An HTTP test plays it end to end. Old game flow removed (EG-1002) - Routes POST /customer/spin, GET /customer/games, GET /customer/ferris-wheel, and admin /marketing/games, /marketing/game-prizes, /marketing/rewards, with their handlers, services, processors, repositories, validators, models, contracts, mappers and tests (GamePlayProcessor, SpinGameService, rewards, ...). This also closes RFC §15 findings 1 and 2 (double charge, spinning another org's game). - Tables games, game_prizes, game_plays and rewards stay for ledger history. entities.StringSlice moves to its own file; the omset tracker (unrouted) keeps game_id but no longer embeds the old game response. games.is_active dropped (EG-1003) - Migration 000115; nothing reads metadata.coin_cost any more. The EnakPoint integration docs now point at /customer/enakgame. The Postgres tests were not run: no test database here. Migration 000115 has not been run anywhere. The customer app must stop calling the removed endpoints before this is deployed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
296708244e
commit
18e87398bb
@@ -3,12 +3,14 @@ package processor
|
||||
import (
|
||||
"encoding/json"
|
||||
"math"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"apskel-pos-be/internal/constants"
|
||||
"apskel-pos-be/internal/models"
|
||||
)
|
||||
|
||||
// fixedRNG returns the given draws in turn.
|
||||
@@ -179,3 +181,49 @@ func TestRewardCalculator_Scale(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, math.MaxInt64/15_000*15_000/10_000, v)
|
||||
}
|
||||
|
||||
// EG-1001: a spin wheel is a PROBABILITY game whose entries carry labels.
|
||||
func TestRewardCalculator_SpinPrizes(t *testing.T) {
|
||||
rules := json.RawMessage(`{"table": [
|
||||
{"weight": 50, "amount": 0, "label": "Zonk"},
|
||||
{"weight": 45, "amount": 5, "label": "5 Coin"},
|
||||
{"weight": 5, "amount": 50}]}`)
|
||||
c, err := RewardCalculatorFor(constants.GameRewardTypeProbability)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, c.Validate(rules))
|
||||
|
||||
prizes, err := probabilityPrizes(rules)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []models.GamePrize{
|
||||
{Entry: 1, Label: ptr("Zonk"), Amount: 0}, {Entry: 2, Label: ptr("5 Coin"), Amount: 5}, {Entry: 3, Amount: 50},
|
||||
}, prizes, "no weights")
|
||||
|
||||
base, detail, err := c.Calculate(rules, SessionResult{}, &fixedRNG{draws: []int64{60}})
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, 5, base)
|
||||
assert.Equal(t, &models.GamePrize{Entry: 2, Label: ptr("5 Coin"), Amount: 5}, drawnPrize(constants.GameRewardTypeProbability, base, detail))
|
||||
|
||||
// As read back from reward_breakdown.
|
||||
stored, err := json.Marshal(detail)
|
||||
require.NoError(t, err)
|
||||
var back map[string]any
|
||||
require.NoError(t, json.Unmarshal(stored, &back))
|
||||
assert.Equal(t, &models.GamePrize{Entry: 2, Label: ptr("5 Coin"), Amount: 5}, drawnPrize(constants.GameRewardTypeProbability, base, back))
|
||||
|
||||
_, detail, err = c.Calculate(rules, SessionResult{}, &fixedRNG{draws: []int64{99}})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, &models.GamePrize{Entry: 3, Amount: 50}, drawnPrize(constants.GameRewardTypeProbability, 50, detail), "no label")
|
||||
assert.Nil(t, drawnPrize(constants.GameRewardTypeFixed, 5, map[string]any{"reward_type": "FIXED"}))
|
||||
assert.Nil(t, drawnPrize(constants.GameRewardTypeProbability, 0, nil), "rejected: nothing drawn")
|
||||
|
||||
scaled, err := c.Scale(rules, 9_000)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, string(scaled), `"label":"Zonk"`, "scaling keeps the labels")
|
||||
|
||||
for name, bad := range map[string]string{
|
||||
"empty label": `{"table": [{"weight": 1, "amount": 1, "label": " "}]}`,
|
||||
"label too long": `{"table": [{"weight": 1, "amount": 1, "label": "` + strings.Repeat("x", 101) + `"}]}`,
|
||||
} {
|
||||
assert.ErrorIs(t, c.Validate(json.RawMessage(bad)), ErrInvalidRewardRules, name)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user