@omnitronix/bonnys-fortune-game-engine 1.3.2 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -15
- package/dist/__tests__/base-game.test.js +255 -0
- package/dist/__tests__/base-game.test.js.map +1 -0
- package/dist/__tests__/bonnys-fortune-v1.game-engine.test.js +1 -1
- package/dist/__tests__/bonus-handlers.test.js +303 -0
- package/dist/__tests__/bonus-handlers.test.js.map +1 -0
- package/dist/__tests__/bonus-meters.test.js +508 -0
- package/dist/__tests__/bonus-meters.test.js.map +1 -0
- package/dist/__tests__/collect-feature-handler.test.js +386 -0
- package/dist/__tests__/collect-feature-handler.test.js.map +1 -0
- package/dist/__tests__/comprehensive.test.js +747 -0
- package/dist/__tests__/comprehensive.test.js.map +1 -0
- package/dist/__tests__/error-paths.test.js +373 -0
- package/dist/__tests__/error-paths.test.js.map +1 -0
- package/dist/__tests__/helpers/test-engine-factory.js +268 -0
- package/dist/__tests__/helpers/test-engine-factory.js.map +1 -0
- package/dist/__tests__/rng-gli19-compliance.test.js +292 -0
- package/dist/__tests__/rng-gli19-compliance.test.js.map +1 -0
- package/dist/__tests__/rng-security.test.js +383 -0
- package/dist/__tests__/rng-security.test.js.map +1 -0
- package/dist/__tests__/rtp-simulation.test.js +242 -0
- package/dist/__tests__/rtp-simulation.test.js.map +1 -0
- package/dist/__tests__/state-transitions.test.js +307 -0
- package/dist/__tests__/state-transitions.test.js.map +1 -0
- package/dist/__tests__/steering-fortune-handler.test.js +286 -0
- package/dist/__tests__/steering-fortune-handler.test.js.map +1 -0
- package/dist/__tests__/symbol-distribution.test.js +195 -0
- package/dist/__tests__/symbol-distribution.test.js.map +1 -0
- package/dist/__tests__/treasure-hunt-handler.test.js +295 -0
- package/dist/__tests__/treasure-hunt-handler.test.js.map +1 -0
- package/dist/__tests__/win-calculator.test.js +515 -0
- package/dist/__tests__/win-calculator.test.js.map +1 -0
- package/dist/bonnys-fortune-v1.game-engine.js +14 -25
- package/dist/bonnys-fortune-v1.game-engine.js.map +1 -1
- package/dist/config/game-logic-config/game-logic-config.js +0 -2
- package/dist/config/game-logic-config/game-logic-config.js.map +1 -1
- package/dist/domain/types/{cheat-trigger-bonus-command.js → debug-trigger-bonus-command.js} +1 -1
- package/dist/domain/types/debug-trigger-bonus-command.js.map +1 -0
- package/dist/domain/types/{cheat-trigger-bonus-request.dto.js → debug-trigger-bonus-request.dto.js} +1 -1
- package/dist/domain/types/debug-trigger-bonus-request.dto.js.map +1 -0
- package/dist/domain/types/{cheat-update-bonus-meter-progress-command.js → debug-update-bonus-meter-progress-command.js} +1 -1
- package/dist/domain/types/debug-update-bonus-meter-progress-command.js.map +1 -0
- package/dist/domain/types/{cheat-update-bonus-meter-progress-request.dto.js → debug-update-bonus-meter-progress-request.dto.js} +1 -1
- package/dist/domain/types/debug-update-bonus-meter-progress-request.dto.js.map +1 -0
- package/dist/index.js.map +1 -1
- package/dist/logic/bonnys-fortune.game-logic.js +37 -17
- package/dist/logic/bonnys-fortune.game-logic.js.map +1 -1
- package/dist/logic/handlers/base-game.handler.js +1 -5
- package/dist/logic/handlers/base-game.handler.js.map +1 -1
- package/dist/rng/DummyRngClient.js +8 -1
- package/dist/rng/DummyRngClient.js.map +1 -1
- package/dist/rng/rng-client.factory.js.map +1 -1
- package/dist/validation/bonnys-fortune/bonnys-fortune-config.dto.js +0 -9
- package/dist/validation/bonnys-fortune/bonnys-fortune-config.dto.js.map +1 -1
- package/package.json +6 -6
- package/dist/__tests__/bonnys-fortune-v1.game-engine.test.d.ts +0 -1
- package/dist/bonnys-fortune-v1.game-engine.d.ts +0 -36
- package/dist/config/game-logic-config/file-system.game-logic-config-loader.d.ts +0 -4
- package/dist/config/game-logic-config/game-logic-config-loader.d.ts +0 -3
- package/dist/config/game-logic-config/game-logic-config.d.ts +0 -2
- package/dist/config/reel-strips-config/file-system.reel-strips-config-loader.d.ts +0 -4
- package/dist/config/reel-strips-config/reel-strip-option.dto.d.ts +0 -4
- package/dist/config/reel-strips-config/reel-strips-config-loader.d.ts +0 -3
- package/dist/config/reel-strips-config/reel-strips-config.dto.d.ts +0 -5
- package/dist/config/reel-strips-config/reel.dto.d.ts +0 -5
- package/dist/domain/game-round.types.d.ts +0 -9
- package/dist/domain/mappers/reel-strips-config.mapper.d.ts +0 -4
- package/dist/domain/reel-strip-option.d.ts +0 -7
- package/dist/domain/reel-strips-config.d.ts +0 -9
- package/dist/domain/reel.d.ts +0 -8
- package/dist/domain/types/cheat-trigger-bonus-command.d.ts +0 -5
- package/dist/domain/types/cheat-trigger-bonus-command.js.map +0 -1
- package/dist/domain/types/cheat-trigger-bonus-request.dto.d.ts +0 -5
- package/dist/domain/types/cheat-trigger-bonus-request.dto.js.map +0 -1
- package/dist/domain/types/cheat-update-bonus-meter-progress-command.d.ts +0 -5
- package/dist/domain/types/cheat-update-bonus-meter-progress-command.js.map +0 -1
- package/dist/domain/types/cheat-update-bonus-meter-progress-request.dto.d.ts +0 -5
- package/dist/domain/types/cheat-update-bonus-meter-progress-request.dto.js.map +0 -1
- package/dist/domain/types/game-spin-command.d.ts +0 -8
- package/dist/domain/types/game-spin-input.dto.d.ts +0 -8
- package/dist/domain/types/game-spin-output.dto.d.ts +0 -119
- package/dist/domain/types/game-symbols.response.dto.d.ts +0 -10
- package/dist/domain/types/reel-strip-option.dto.d.ts +0 -4
- package/dist/domain/types/reel-strips-config.dto.d.ts +0 -5
- package/dist/domain/types/reel.dto.d.ts +0 -5
- package/dist/domain/types/start-bonus-round-command.d.ts +0 -8
- package/dist/domain/types/start-bonus-round-input.dto.d.ts +0 -10
- package/dist/game-engine.interface.d.ts +0 -41
- package/dist/helpers/generate-hash.d.ts +0 -1
- package/dist/helpers/number-helper.d.ts +0 -23
- package/dist/helpers/optional-boolean-mapper.d.ts +0 -1
- package/dist/helpers/uuid-helper.d.ts +0 -3
- package/dist/helpers/validation-helper.d.ts +0 -14
- package/dist/index.d.ts +0 -2
- package/dist/logger/logger.d.ts +0 -22
- package/dist/logger/logger.js +0 -140
- package/dist/logger/logger.js.map +0 -1
- package/dist/logic/bonnys-fortune.game-logic.d.ts +0 -32
- package/dist/logic/bonnys-fortune.spin-generator.d.ts +0 -19
- package/dist/logic/bonnys-fortune.types.d.ts +0 -170
- package/dist/logic/bonnys-fortune.win-calculator.d.ts +0 -10
- package/dist/logic/game-logic-config.interface.d.ts +0 -180
- package/dist/logic/handlers/base-game.handler.d.ts +0 -15
- package/dist/logic/handlers/collect-feature-bonus.handler.d.ts +0 -13
- package/dist/logic/handlers/free-spins-bonus.handler.d.ts +0 -11
- package/dist/logic/handlers/steering-to-the-fortune-bonus.handler.d.ts +0 -11
- package/dist/logic/handlers/treasure-hunt-bonus.handler.d.ts +0 -15
- package/dist/rng/DummyRngClient.d.ts +0 -18
- package/dist/rng/rng-client.factory.d.ts +0 -7
- package/dist/rng/rng-client.interface.d.ts +0 -16
- package/dist/rng/rng-service.d.ts +0 -26
- package/dist/validation/abstract-game-logic-config.validator.d.ts +0 -4
- package/dist/validation/bonnys-fortune/bonnys-fortune-config.dto.d.ts +0 -131
- package/dist/validation/bonnys-fortune/bonnys-fortune-config.validator.d.ts +0 -5
- package/dist/validation/custom-decorators/IsNestedIntArray.d.ts +0 -2
- package/dist/validation/game-logic-config-validation.service.d.ts +0 -8
- package/dist/validation/reel-strips-config-validation.service.d.ts +0 -5
- package/src/__tests__/bonnys-fortune-v1.game-engine.test.ts +0 -80
- package/src/bonnys-fortune-v1.game-engine.ts +0 -314
- package/src/config/game-logic-config/file-system.game-logic-config-loader.ts +0 -27
- package/src/config/game-logic-config/game-logic-config-loader.ts +0 -3
- package/src/config/game-logic-config/game-logic-config.ts +0 -382
- package/src/config/reel-strips-config/file-system.reel-strips-config-loader.ts +0 -43
- package/src/config/reel-strips-config/reel-strip-option.dto.ts +0 -13
- package/src/config/reel-strips-config/reel-strips-config-loader.ts +0 -9
- package/src/config/reel-strips-config/reel-strips-config.dto.ts +0 -16
- package/src/config/reel-strips-config/reel.dto.ts +0 -16
- package/src/config/reel-strips-config/reels-BASE.csv +0 -52
- package/src/config/reel-strips-config/reels-BONUS.csv +0 -52
- package/src/domain/game-round.types.ts +0 -10
- package/src/domain/mappers/reel-strips-config.mapper.ts +0 -16
- package/src/domain/reel-strip-option.ts +0 -15
- package/src/domain/reel-strips-config.ts +0 -21
- package/src/domain/reel.ts +0 -17
- package/src/domain/types/cheat-trigger-bonus-command.ts +0 -5
- package/src/domain/types/cheat-trigger-bonus-request.dto.ts +0 -5
- package/src/domain/types/cheat-update-bonus-meter-progress-command.ts +0 -6
- package/src/domain/types/cheat-update-bonus-meter-progress-request.dto.ts +0 -5
- package/src/domain/types/game-spin-command.ts +0 -8
- package/src/domain/types/game-spin-input.dto.ts +0 -8
- package/src/domain/types/game-spin-output.dto.ts +0 -142
- package/src/domain/types/game-symbols.response.dto.ts +0 -11
- package/src/domain/types/reel-strip-option.dto.ts +0 -13
- package/src/domain/types/reel-strips-config.dto.ts +0 -15
- package/src/domain/types/reel.dto.ts +0 -15
- package/src/domain/types/start-bonus-round-command.ts +0 -8
- package/src/domain/types/start-bonus-round-input.dto.ts +0 -10
- package/src/game-engine.interface.ts +0 -59
- package/src/helpers/generate-hash.ts +0 -5
- package/src/helpers/number-helper.ts +0 -41
- package/src/helpers/optional-boolean-mapper.ts +0 -5
- package/src/helpers/uuid-helper.ts +0 -7
- package/src/helpers/validation-helper.ts +0 -27
- package/src/index.ts +0 -3
- package/src/logger/logger.ts +0 -178
- package/src/logic/bonnys-fortune.game-logic.ts +0 -490
- package/src/logic/bonnys-fortune.spin-generator.ts +0 -277
- package/src/logic/bonnys-fortune.types.ts +0 -223
- package/src/logic/bonnys-fortune.win-calculator.ts +0 -210
- package/src/logic/game-logic-config.interface.ts +0 -176
- package/src/logic/handlers/base-game.handler.ts +0 -221
- package/src/logic/handlers/collect-feature-bonus.handler.ts +0 -301
- package/src/logic/handlers/free-spins-bonus.handler.ts +0 -119
- package/src/logic/handlers/steering-to-the-fortune-bonus.handler.ts +0 -118
- package/src/logic/handlers/treasure-hunt-bonus.handler.ts +0 -232
- package/src/rng/DummyRngClient.ts +0 -108
- package/src/rng/rng-client.factory.ts +0 -27
- package/src/rng/rng-client.interface.ts +0 -38
- package/src/rng/rng-service.ts +0 -130
- package/src/validation/abstract-game-logic-config.validator.ts +0 -20
- package/src/validation/bonnys-fortune/bonnys-fortune-config.dto.ts +0 -379
- package/src/validation/bonnys-fortune/bonnys-fortune-config.validator.ts +0 -8
- package/src/validation/custom-decorators/IsNestedIntArray.ts +0 -29
- package/src/validation/game-logic-config-validation.service.ts +0 -28
- package/src/validation/reel-strips-config-validation.service.ts +0 -29
|
@@ -0,0 +1,747 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Comprehensive Test Suite for Bonny's Fortune Game Engine
|
|
4
|
+
*
|
|
5
|
+
* Coverage:
|
|
6
|
+
* - Session initialization
|
|
7
|
+
* - Base game spins with bonus meter accumulation
|
|
8
|
+
* - bonusGame1 (Treasure Hunt) flow
|
|
9
|
+
* - bonusGame2 (Free Spins) flow
|
|
10
|
+
* - bonusGame3 (Steering to the Fortune) flow
|
|
11
|
+
* - collectFeature flow
|
|
12
|
+
* - Bonus meter progression
|
|
13
|
+
* - Spin/Bonus combinations
|
|
14
|
+
* - State persistence
|
|
15
|
+
* - Error handling
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
const bonnys_fortune_v1_game_engine_1 = require("../bonnys-fortune-v1.game-engine");
|
|
19
|
+
describe("Bonny's Fortune - Comprehensive Test Suite", () => {
|
|
20
|
+
let engine;
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
engine = new bonnys_fortune_v1_game_engine_1.BonnysFortuneV1GameEngine();
|
|
23
|
+
});
|
|
24
|
+
// ============================================================
|
|
25
|
+
// SESSION INITIALIZATION
|
|
26
|
+
// ============================================================
|
|
27
|
+
describe('Session Initialization', () => {
|
|
28
|
+
it('should initialize session with default config', async () => {
|
|
29
|
+
const command = {
|
|
30
|
+
id: 'init-1',
|
|
31
|
+
type: 'INIT_SESSION_STATE',
|
|
32
|
+
payload: {
|
|
33
|
+
betAmount: 1.0,
|
|
34
|
+
currency: 'EUR',
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
const result = await engine.processCommand(null, null, command);
|
|
38
|
+
expect(result.success).toBe(true);
|
|
39
|
+
expect(result.publicState).toBeDefined();
|
|
40
|
+
expect(result.privateState).toBeDefined();
|
|
41
|
+
});
|
|
42
|
+
it('should initialize with bonus meters', async () => {
|
|
43
|
+
const result = await engine.processCommand(null, null, {
|
|
44
|
+
id: 'init-meters',
|
|
45
|
+
type: 'INIT_SESSION_STATE',
|
|
46
|
+
payload: { betAmount: 1.0 },
|
|
47
|
+
});
|
|
48
|
+
expect(result.success).toBe(true);
|
|
49
|
+
expect(result.publicState.bonusMeters).toBeDefined();
|
|
50
|
+
});
|
|
51
|
+
it('should initialize session with various bet amounts', async () => {
|
|
52
|
+
const betAmounts = [0.5, 1.0, 2.0, 5.0, 10.0];
|
|
53
|
+
for (const betAmount of betAmounts) {
|
|
54
|
+
const result = await engine.processCommand(null, null, {
|
|
55
|
+
id: `init-${betAmount}`,
|
|
56
|
+
type: 'INIT_SESSION_STATE',
|
|
57
|
+
payload: { betAmount },
|
|
58
|
+
});
|
|
59
|
+
expect(result.success).toBe(true);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
it('should return game engine info', () => {
|
|
63
|
+
const info = engine.getGameEngineInfo();
|
|
64
|
+
expect(info.gameCode).toBe('bonnys-fortune');
|
|
65
|
+
expect(info.version).toBeDefined();
|
|
66
|
+
expect(info.rtp).toBeGreaterThan(90);
|
|
67
|
+
expect(info.gameType).toBe('slot');
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
// ============================================================
|
|
71
|
+
// BASE GAME SPINS
|
|
72
|
+
// ============================================================
|
|
73
|
+
describe('Base Game Spins', () => {
|
|
74
|
+
let publicState;
|
|
75
|
+
let privateState;
|
|
76
|
+
beforeEach(async () => {
|
|
77
|
+
const initResult = await engine.processCommand(null, null, {
|
|
78
|
+
id: 'init',
|
|
79
|
+
type: 'INIT_SESSION_STATE',
|
|
80
|
+
payload: { betAmount: 1.0 },
|
|
81
|
+
});
|
|
82
|
+
publicState = initResult.publicState;
|
|
83
|
+
privateState = initResult.privateState;
|
|
84
|
+
});
|
|
85
|
+
it('should execute a base game spin', async () => {
|
|
86
|
+
const result = await engine.processCommand(publicState, privateState, {
|
|
87
|
+
id: 'spin-1',
|
|
88
|
+
type: 'SPIN',
|
|
89
|
+
payload: {
|
|
90
|
+
sessionId: 'test-session',
|
|
91
|
+
betAmount: 1.0,
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
expect(result.success).toBe(true);
|
|
95
|
+
expect(result.publicState).toBeDefined();
|
|
96
|
+
expect(result.privateState).toBeDefined();
|
|
97
|
+
expect(result.outcome).toBeDefined();
|
|
98
|
+
});
|
|
99
|
+
it('should update bonus meters on spin', async () => {
|
|
100
|
+
const result = await engine.processCommand(publicState, privateState, {
|
|
101
|
+
id: 'spin-meter',
|
|
102
|
+
type: 'SPIN',
|
|
103
|
+
payload: {
|
|
104
|
+
sessionId: 'test',
|
|
105
|
+
betAmount: 1.0,
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
expect(result.success).toBe(true);
|
|
109
|
+
expect(result.publicState.bonusMeters).toBeDefined();
|
|
110
|
+
});
|
|
111
|
+
it('should handle multiple consecutive spins', async () => {
|
|
112
|
+
let currentPublic = publicState;
|
|
113
|
+
let currentPrivate = privateState;
|
|
114
|
+
for (let i = 0; i < 10; i++) {
|
|
115
|
+
const result = await engine.processCommand(currentPublic, currentPrivate, {
|
|
116
|
+
id: `spin-${i}`,
|
|
117
|
+
type: 'SPIN',
|
|
118
|
+
payload: {
|
|
119
|
+
sessionId: 'test',
|
|
120
|
+
betAmount: 1.0,
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
expect(result.success).toBe(true);
|
|
124
|
+
currentPublic = result.publicState;
|
|
125
|
+
currentPrivate = result.privateState;
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
it('should accumulate bonus meter progress across spins', async () => {
|
|
129
|
+
let currentPublic = publicState;
|
|
130
|
+
let currentPrivate = privateState;
|
|
131
|
+
// Perform multiple spins to accumulate meter progress
|
|
132
|
+
for (let i = 0; i < 10; i++) {
|
|
133
|
+
const result = await engine.processCommand(currentPublic, currentPrivate, {
|
|
134
|
+
id: `spin-${i}`,
|
|
135
|
+
type: 'SPIN',
|
|
136
|
+
payload: {
|
|
137
|
+
sessionId: 'test',
|
|
138
|
+
betAmount: 1.0,
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
expect(result.success).toBe(true);
|
|
142
|
+
currentPublic = result.publicState;
|
|
143
|
+
currentPrivate = result.privateState;
|
|
144
|
+
}
|
|
145
|
+
// Bonus meters should exist
|
|
146
|
+
expect(currentPublic.bonusMeters).toBeDefined();
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
// ============================================================
|
|
150
|
+
// BONUS GAME 1 - TREASURE HUNT
|
|
151
|
+
// ============================================================
|
|
152
|
+
describe('bonusGame1 (Treasure Hunt) Flow', () => {
|
|
153
|
+
let publicState;
|
|
154
|
+
let privateState;
|
|
155
|
+
beforeEach(async () => {
|
|
156
|
+
const initResult = await engine.processCommand(null, null, {
|
|
157
|
+
id: 'init',
|
|
158
|
+
type: 'INIT_SESSION_STATE',
|
|
159
|
+
payload: { betAmount: 1.0 },
|
|
160
|
+
});
|
|
161
|
+
publicState = initResult.publicState;
|
|
162
|
+
privateState = initResult.privateState;
|
|
163
|
+
});
|
|
164
|
+
it('should trigger bonusGame1 via debug command', async () => {
|
|
165
|
+
const result = await engine.processCommand(publicState, privateState, {
|
|
166
|
+
id: 'debug-bg1',
|
|
167
|
+
type: 'DEBUG_TRIGGER_BONUS',
|
|
168
|
+
payload: {
|
|
169
|
+
sessionId: 'test-session',
|
|
170
|
+
bonusType: 'bonusGame1',
|
|
171
|
+
betAmount: 1.0,
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
expect(result.success).toBe(true);
|
|
175
|
+
});
|
|
176
|
+
it('should complete full bonusGame1 flow: trigger → continue spinning', async () => {
|
|
177
|
+
// Trigger bonus
|
|
178
|
+
const triggerResult = await engine.processCommand(publicState, privateState, {
|
|
179
|
+
id: 'trigger-bg1',
|
|
180
|
+
type: 'DEBUG_TRIGGER_BONUS',
|
|
181
|
+
payload: {
|
|
182
|
+
sessionId: 'test',
|
|
183
|
+
bonusType: 'bonusGame1',
|
|
184
|
+
betAmount: 1.0,
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
expect(triggerResult.success).toBe(true);
|
|
188
|
+
// Continue with spins after bonus trigger
|
|
189
|
+
let currentPublic = triggerResult.publicState;
|
|
190
|
+
let currentPrivate = triggerResult.privateState;
|
|
191
|
+
for (let i = 0; i < 3; i++) {
|
|
192
|
+
const spinResult = await engine.processCommand(currentPublic, currentPrivate, {
|
|
193
|
+
id: `spin-bg1-${i}`,
|
|
194
|
+
type: 'SPIN',
|
|
195
|
+
payload: {
|
|
196
|
+
sessionId: 'test',
|
|
197
|
+
betAmount: 1.0,
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
expect(spinResult.success).toBe(true);
|
|
201
|
+
currentPublic = spinResult.publicState;
|
|
202
|
+
currentPrivate = spinResult.privateState;
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
it('should handle multiple bonusGame1 triggers', async () => {
|
|
206
|
+
let currentPublic = publicState;
|
|
207
|
+
let currentPrivate = privateState;
|
|
208
|
+
for (let i = 0; i < 3; i++) {
|
|
209
|
+
const result = await engine.processCommand(currentPublic, currentPrivate, {
|
|
210
|
+
id: `trigger-bg1-${i}`,
|
|
211
|
+
type: 'DEBUG_TRIGGER_BONUS',
|
|
212
|
+
payload: {
|
|
213
|
+
sessionId: 'test',
|
|
214
|
+
bonusType: 'bonusGame1',
|
|
215
|
+
betAmount: 1.0,
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
expect(result.success).toBe(true);
|
|
219
|
+
currentPublic = result.publicState;
|
|
220
|
+
currentPrivate = result.privateState;
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
// ============================================================
|
|
225
|
+
// BONUS GAME 2 - FREE SPINS
|
|
226
|
+
// ============================================================
|
|
227
|
+
describe('bonusGame2 (Free Spins) Flow', () => {
|
|
228
|
+
let publicState;
|
|
229
|
+
let privateState;
|
|
230
|
+
beforeEach(async () => {
|
|
231
|
+
const initResult = await engine.processCommand(null, null, {
|
|
232
|
+
id: 'init',
|
|
233
|
+
type: 'INIT_SESSION_STATE',
|
|
234
|
+
payload: { betAmount: 1.0 },
|
|
235
|
+
});
|
|
236
|
+
publicState = initResult.publicState;
|
|
237
|
+
privateState = initResult.privateState;
|
|
238
|
+
});
|
|
239
|
+
it('should trigger bonusGame2 via debug command', async () => {
|
|
240
|
+
const result = await engine.processCommand(publicState, privateState, {
|
|
241
|
+
id: 'debug-bg2',
|
|
242
|
+
type: 'DEBUG_TRIGGER_BONUS',
|
|
243
|
+
payload: {
|
|
244
|
+
sessionId: 'test-session',
|
|
245
|
+
bonusType: 'bonusGame2',
|
|
246
|
+
betAmount: 1.0,
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
expect(result.success).toBe(true);
|
|
250
|
+
});
|
|
251
|
+
it('should complete full bonusGame2 flow: trigger → continue with spins', async () => {
|
|
252
|
+
// Trigger bonus
|
|
253
|
+
const triggerResult = await engine.processCommand(publicState, privateState, {
|
|
254
|
+
id: 'trigger-bg2',
|
|
255
|
+
type: 'DEBUG_TRIGGER_BONUS',
|
|
256
|
+
payload: {
|
|
257
|
+
sessionId: 'test',
|
|
258
|
+
bonusType: 'bonusGame2',
|
|
259
|
+
betAmount: 1.0,
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
expect(triggerResult.success).toBe(true);
|
|
263
|
+
// Continue with spins after bonus trigger
|
|
264
|
+
let currentPublic = triggerResult.publicState;
|
|
265
|
+
let currentPrivate = triggerResult.privateState;
|
|
266
|
+
for (let i = 0; i < 3; i++) {
|
|
267
|
+
const spinResult = await engine.processCommand(currentPublic, currentPrivate, {
|
|
268
|
+
id: `spin-bg2-${i}`,
|
|
269
|
+
type: 'SPIN',
|
|
270
|
+
payload: {
|
|
271
|
+
sessionId: 'test',
|
|
272
|
+
betAmount: 1.0,
|
|
273
|
+
},
|
|
274
|
+
});
|
|
275
|
+
expect(spinResult.success).toBe(true);
|
|
276
|
+
currentPublic = spinResult.publicState;
|
|
277
|
+
currentPrivate = spinResult.privateState;
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
// ============================================================
|
|
282
|
+
// BONUS GAME 3 - STEERING TO THE FORTUNE
|
|
283
|
+
// ============================================================
|
|
284
|
+
describe('bonusGame3 (Steering to the Fortune) Flow', () => {
|
|
285
|
+
let publicState;
|
|
286
|
+
let privateState;
|
|
287
|
+
beforeEach(async () => {
|
|
288
|
+
const initResult = await engine.processCommand(null, null, {
|
|
289
|
+
id: 'init',
|
|
290
|
+
type: 'INIT_SESSION_STATE',
|
|
291
|
+
payload: { betAmount: 1.0 },
|
|
292
|
+
});
|
|
293
|
+
publicState = initResult.publicState;
|
|
294
|
+
privateState = initResult.privateState;
|
|
295
|
+
});
|
|
296
|
+
it('should trigger bonusGame3 via debug command', async () => {
|
|
297
|
+
const result = await engine.processCommand(publicState, privateState, {
|
|
298
|
+
id: 'debug-bg3',
|
|
299
|
+
type: 'DEBUG_TRIGGER_BONUS',
|
|
300
|
+
payload: {
|
|
301
|
+
sessionId: 'test-session',
|
|
302
|
+
bonusType: 'bonusGame3',
|
|
303
|
+
betAmount: 1.0,
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
expect(result.success).toBe(true);
|
|
307
|
+
});
|
|
308
|
+
it('should complete full bonusGame3 flow: trigger → continue with spins', async () => {
|
|
309
|
+
// Trigger bonus
|
|
310
|
+
const triggerResult = await engine.processCommand(publicState, privateState, {
|
|
311
|
+
id: 'trigger-bg3',
|
|
312
|
+
type: 'DEBUG_TRIGGER_BONUS',
|
|
313
|
+
payload: {
|
|
314
|
+
sessionId: 'test',
|
|
315
|
+
bonusType: 'bonusGame3',
|
|
316
|
+
betAmount: 1.0,
|
|
317
|
+
},
|
|
318
|
+
});
|
|
319
|
+
expect(triggerResult.success).toBe(true);
|
|
320
|
+
// Continue with spins after bonus trigger
|
|
321
|
+
let currentPublic = triggerResult.publicState;
|
|
322
|
+
let currentPrivate = triggerResult.privateState;
|
|
323
|
+
for (let i = 0; i < 3; i++) {
|
|
324
|
+
const spinResult = await engine.processCommand(currentPublic, currentPrivate, {
|
|
325
|
+
id: `spin-bg3-${i}`,
|
|
326
|
+
type: 'SPIN',
|
|
327
|
+
payload: {
|
|
328
|
+
sessionId: 'test',
|
|
329
|
+
betAmount: 1.0,
|
|
330
|
+
},
|
|
331
|
+
});
|
|
332
|
+
expect(spinResult.success).toBe(true);
|
|
333
|
+
currentPublic = spinResult.publicState;
|
|
334
|
+
currentPrivate = spinResult.privateState;
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
// ============================================================
|
|
339
|
+
// COLLECT FEATURE
|
|
340
|
+
// ============================================================
|
|
341
|
+
describe('collectFeature Flow', () => {
|
|
342
|
+
let publicState;
|
|
343
|
+
let privateState;
|
|
344
|
+
beforeEach(async () => {
|
|
345
|
+
const initResult = await engine.processCommand(null, null, {
|
|
346
|
+
id: 'init',
|
|
347
|
+
type: 'INIT_SESSION_STATE',
|
|
348
|
+
payload: { betAmount: 1.0 },
|
|
349
|
+
});
|
|
350
|
+
publicState = initResult.publicState;
|
|
351
|
+
privateState = initResult.privateState;
|
|
352
|
+
});
|
|
353
|
+
it('should trigger collectFeature via debug command', async () => {
|
|
354
|
+
const result = await engine.processCommand(publicState, privateState, {
|
|
355
|
+
id: 'debug-collect',
|
|
356
|
+
type: 'DEBUG_TRIGGER_BONUS',
|
|
357
|
+
payload: {
|
|
358
|
+
sessionId: 'test-session',
|
|
359
|
+
bonusType: 'collectFeature',
|
|
360
|
+
betAmount: 1.0,
|
|
361
|
+
},
|
|
362
|
+
});
|
|
363
|
+
expect(result.success).toBe(true);
|
|
364
|
+
});
|
|
365
|
+
it('should handle collectFeature with various bet amounts', async () => {
|
|
366
|
+
const betAmounts = [1.0, 2.0, 5.0];
|
|
367
|
+
for (const betAmount of betAmounts) {
|
|
368
|
+
const init = await engine.processCommand(null, null, {
|
|
369
|
+
id: `init-${betAmount}`,
|
|
370
|
+
type: 'INIT_SESSION_STATE',
|
|
371
|
+
payload: { betAmount },
|
|
372
|
+
});
|
|
373
|
+
const result = await engine.processCommand(init.publicState, init.privateState, {
|
|
374
|
+
id: `collect-${betAmount}`,
|
|
375
|
+
type: 'DEBUG_TRIGGER_BONUS',
|
|
376
|
+
payload: {
|
|
377
|
+
sessionId: 'test',
|
|
378
|
+
bonusType: 'collectFeature',
|
|
379
|
+
betAmount,
|
|
380
|
+
},
|
|
381
|
+
});
|
|
382
|
+
expect(result.success).toBe(true);
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
});
|
|
386
|
+
// ============================================================
|
|
387
|
+
// BONUS METER MANIPULATION
|
|
388
|
+
// ============================================================
|
|
389
|
+
describe('Bonus Meter Manipulation', () => {
|
|
390
|
+
let publicState;
|
|
391
|
+
let privateState;
|
|
392
|
+
beforeEach(async () => {
|
|
393
|
+
const initResult = await engine.processCommand(null, null, {
|
|
394
|
+
id: 'init',
|
|
395
|
+
type: 'INIT_SESSION_STATE',
|
|
396
|
+
payload: { betAmount: 1.0 },
|
|
397
|
+
});
|
|
398
|
+
publicState = initResult.publicState;
|
|
399
|
+
privateState = initResult.privateState;
|
|
400
|
+
});
|
|
401
|
+
it('should update bonus meter progress via debug command', async () => {
|
|
402
|
+
const result = await engine.processCommand(publicState, privateState, {
|
|
403
|
+
id: 'update-meter',
|
|
404
|
+
type: 'DEBUG_UPDATE_BONUS_METER_PROGRESS',
|
|
405
|
+
payload: {
|
|
406
|
+
sessionId: 'test',
|
|
407
|
+
bonusType: 'bonusGame1',
|
|
408
|
+
progress: 50,
|
|
409
|
+
},
|
|
410
|
+
});
|
|
411
|
+
expect(result).toBeDefined();
|
|
412
|
+
expect(result.success).toBe(true);
|
|
413
|
+
});
|
|
414
|
+
it('should track meter progress across multiple updates', async () => {
|
|
415
|
+
let currentPublic = publicState;
|
|
416
|
+
let currentPrivate = privateState;
|
|
417
|
+
const bonusTypes = ['bonusGame1', 'bonusGame2', 'bonusGame3'];
|
|
418
|
+
for (const bonusType of bonusTypes) {
|
|
419
|
+
const result = await engine.processCommand(currentPublic, currentPrivate, {
|
|
420
|
+
id: `update-${bonusType}`,
|
|
421
|
+
type: 'DEBUG_UPDATE_BONUS_METER_PROGRESS',
|
|
422
|
+
payload: {
|
|
423
|
+
sessionId: 'test',
|
|
424
|
+
bonusType,
|
|
425
|
+
progress: 25,
|
|
426
|
+
},
|
|
427
|
+
});
|
|
428
|
+
expect(result).toBeDefined();
|
|
429
|
+
expect(result.success).toBe(true);
|
|
430
|
+
currentPublic = result.publicState || currentPublic;
|
|
431
|
+
currentPrivate = result.privateState || currentPrivate;
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
});
|
|
435
|
+
// ============================================================
|
|
436
|
+
// SPIN/BONUS COMBINATIONS
|
|
437
|
+
// ============================================================
|
|
438
|
+
describe('Spin/Bonus Combinations', () => {
|
|
439
|
+
let publicState;
|
|
440
|
+
let privateState;
|
|
441
|
+
beforeEach(async () => {
|
|
442
|
+
const initResult = await engine.processCommand(null, null, {
|
|
443
|
+
id: 'init',
|
|
444
|
+
type: 'INIT_SESSION_STATE',
|
|
445
|
+
payload: { betAmount: 1.0 },
|
|
446
|
+
});
|
|
447
|
+
publicState = initResult.publicState;
|
|
448
|
+
privateState = initResult.privateState;
|
|
449
|
+
});
|
|
450
|
+
it('should handle spin → bonus → spin sequence', async () => {
|
|
451
|
+
// Initial spins
|
|
452
|
+
let current = await engine.processCommand(publicState, privateState, {
|
|
453
|
+
id: 'spin-1',
|
|
454
|
+
type: 'SPIN',
|
|
455
|
+
payload: {
|
|
456
|
+
sessionId: 'test',
|
|
457
|
+
betAmount: 1.0,
|
|
458
|
+
},
|
|
459
|
+
});
|
|
460
|
+
expect(current.success).toBe(true);
|
|
461
|
+
current = await engine.processCommand(current.publicState, current.privateState, {
|
|
462
|
+
id: 'spin-2',
|
|
463
|
+
type: 'SPIN',
|
|
464
|
+
payload: {
|
|
465
|
+
sessionId: 'test',
|
|
466
|
+
betAmount: 1.0,
|
|
467
|
+
},
|
|
468
|
+
});
|
|
469
|
+
expect(current.success).toBe(true);
|
|
470
|
+
// Trigger bonus
|
|
471
|
+
const bonusResult = await engine.processCommand(current.publicState, current.privateState, {
|
|
472
|
+
id: 'trigger-bonus',
|
|
473
|
+
type: 'DEBUG_TRIGGER_BONUS',
|
|
474
|
+
payload: {
|
|
475
|
+
sessionId: 'test',
|
|
476
|
+
bonusType: 'bonusGame1',
|
|
477
|
+
betAmount: 1.0,
|
|
478
|
+
},
|
|
479
|
+
});
|
|
480
|
+
expect(bonusResult.success).toBe(true);
|
|
481
|
+
// Continue with more spins
|
|
482
|
+
const afterBonus = await engine.processCommand(bonusResult.publicState, bonusResult.privateState, {
|
|
483
|
+
id: 'spin-after',
|
|
484
|
+
type: 'SPIN',
|
|
485
|
+
payload: {
|
|
486
|
+
sessionId: 'test',
|
|
487
|
+
betAmount: 1.0,
|
|
488
|
+
},
|
|
489
|
+
});
|
|
490
|
+
expect(afterBonus).toBeDefined();
|
|
491
|
+
});
|
|
492
|
+
it('should handle all bonus types in sequence', async () => {
|
|
493
|
+
const bonusTypes = ['bonusGame1', 'bonusGame2', 'bonusGame3', 'collectFeature'];
|
|
494
|
+
let currentPublic = publicState;
|
|
495
|
+
let currentPrivate = privateState;
|
|
496
|
+
for (const bonusType of bonusTypes) {
|
|
497
|
+
// Spin first
|
|
498
|
+
const spin = await engine.processCommand(currentPublic, currentPrivate, {
|
|
499
|
+
id: `spin-${bonusType}`,
|
|
500
|
+
type: 'SPIN',
|
|
501
|
+
payload: {
|
|
502
|
+
sessionId: 'test',
|
|
503
|
+
betAmount: 1.0,
|
|
504
|
+
},
|
|
505
|
+
});
|
|
506
|
+
expect(spin.success).toBe(true);
|
|
507
|
+
// Trigger bonus
|
|
508
|
+
const bonus = await engine.processCommand(spin.publicState, spin.privateState, {
|
|
509
|
+
id: `trigger-${bonusType}`,
|
|
510
|
+
type: 'DEBUG_TRIGGER_BONUS',
|
|
511
|
+
payload: {
|
|
512
|
+
sessionId: 'test',
|
|
513
|
+
bonusType,
|
|
514
|
+
betAmount: 1.0,
|
|
515
|
+
},
|
|
516
|
+
});
|
|
517
|
+
expect(bonus.success).toBe(true);
|
|
518
|
+
currentPublic = bonus.publicState;
|
|
519
|
+
currentPrivate = bonus.privateState;
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
it('should handle rapid bonus type switching', async () => {
|
|
523
|
+
const bonusSequence = [
|
|
524
|
+
'bonusGame1',
|
|
525
|
+
'bonusGame2',
|
|
526
|
+
'bonusGame1',
|
|
527
|
+
'bonusGame3',
|
|
528
|
+
'collectFeature',
|
|
529
|
+
'bonusGame2',
|
|
530
|
+
];
|
|
531
|
+
let currentPublic = publicState;
|
|
532
|
+
let currentPrivate = privateState;
|
|
533
|
+
for (let i = 0; i < bonusSequence.length; i++) {
|
|
534
|
+
const result = await engine.processCommand(currentPublic, currentPrivate, {
|
|
535
|
+
id: `rapid-${i}`,
|
|
536
|
+
type: 'DEBUG_TRIGGER_BONUS',
|
|
537
|
+
payload: {
|
|
538
|
+
sessionId: 'test',
|
|
539
|
+
bonusType: bonusSequence[i],
|
|
540
|
+
betAmount: 1.0,
|
|
541
|
+
},
|
|
542
|
+
});
|
|
543
|
+
expect(result.success).toBe(true);
|
|
544
|
+
currentPublic = result.publicState;
|
|
545
|
+
currentPrivate = result.privateState;
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
it('should handle mixed bet amounts with different bonus types', async () => {
|
|
549
|
+
const configs = [
|
|
550
|
+
{ betAmount: 1.0, bonusType: 'bonusGame1' },
|
|
551
|
+
{ betAmount: 2.0, bonusType: 'bonusGame2' },
|
|
552
|
+
{ betAmount: 5.0, bonusType: 'bonusGame3' },
|
|
553
|
+
{ betAmount: 10.0, bonusType: 'collectFeature' },
|
|
554
|
+
];
|
|
555
|
+
for (const { betAmount, bonusType } of configs) {
|
|
556
|
+
const init = await engine.processCommand(null, null, {
|
|
557
|
+
id: `init-${betAmount}`,
|
|
558
|
+
type: 'INIT_SESSION_STATE',
|
|
559
|
+
payload: { betAmount },
|
|
560
|
+
});
|
|
561
|
+
// Spin
|
|
562
|
+
const spin = await engine.processCommand(init.publicState, init.privateState, {
|
|
563
|
+
id: `spin-${betAmount}`,
|
|
564
|
+
type: 'SPIN',
|
|
565
|
+
payload: {
|
|
566
|
+
sessionId: 'test',
|
|
567
|
+
betAmount,
|
|
568
|
+
},
|
|
569
|
+
});
|
|
570
|
+
expect(spin.success).toBe(true);
|
|
571
|
+
// Trigger bonus
|
|
572
|
+
const bonus = await engine.processCommand(spin.publicState, spin.privateState, {
|
|
573
|
+
id: `bonus-${betAmount}`,
|
|
574
|
+
type: 'DEBUG_TRIGGER_BONUS',
|
|
575
|
+
payload: {
|
|
576
|
+
sessionId: 'test',
|
|
577
|
+
bonusType,
|
|
578
|
+
betAmount,
|
|
579
|
+
},
|
|
580
|
+
});
|
|
581
|
+
expect(bonus.success).toBe(true);
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
});
|
|
585
|
+
// ============================================================
|
|
586
|
+
// DEBUG TRIGGER ERROR CASES
|
|
587
|
+
// ============================================================
|
|
588
|
+
describe('Debug Trigger Error Cases', () => {
|
|
589
|
+
let publicState;
|
|
590
|
+
let privateState;
|
|
591
|
+
beforeEach(async () => {
|
|
592
|
+
const initResult = await engine.processCommand(null, null, {
|
|
593
|
+
id: 'init',
|
|
594
|
+
type: 'INIT_SESSION_STATE',
|
|
595
|
+
payload: { betAmount: 1.0 },
|
|
596
|
+
});
|
|
597
|
+
publicState = initResult.publicState;
|
|
598
|
+
privateState = initResult.privateState;
|
|
599
|
+
});
|
|
600
|
+
it('should accept any bonus type in debug mode', async () => {
|
|
601
|
+
// Note: DEBUG_TRIGGER_BONUS does not validate bonus types
|
|
602
|
+
// It accepts any string as bonusType for flexibility
|
|
603
|
+
const result = await engine.processCommand(publicState, privateState, {
|
|
604
|
+
id: 'any-bonus',
|
|
605
|
+
type: 'DEBUG_TRIGGER_BONUS',
|
|
606
|
+
payload: {
|
|
607
|
+
sessionId: 'test',
|
|
608
|
+
bonusType: 'CUSTOM_BONUS_TYPE',
|
|
609
|
+
betAmount: 1.0,
|
|
610
|
+
},
|
|
611
|
+
});
|
|
612
|
+
expect(result.success).toBe(true);
|
|
613
|
+
});
|
|
614
|
+
it('should throw error without initialized state', async () => {
|
|
615
|
+
// Engine throws TypeError when state is null
|
|
616
|
+
// This is expected behavior - state must be initialized
|
|
617
|
+
await expect(engine.processCommand(null, null, {
|
|
618
|
+
id: 'no-state',
|
|
619
|
+
type: 'DEBUG_TRIGGER_BONUS',
|
|
620
|
+
payload: {
|
|
621
|
+
sessionId: 'test',
|
|
622
|
+
bonusType: 'bonusGame1',
|
|
623
|
+
betAmount: 1.0,
|
|
624
|
+
},
|
|
625
|
+
})).rejects.toThrow();
|
|
626
|
+
});
|
|
627
|
+
});
|
|
628
|
+
// ============================================================
|
|
629
|
+
// SESSION MANAGEMENT
|
|
630
|
+
// ============================================================
|
|
631
|
+
describe('Session Management', () => {
|
|
632
|
+
let publicState;
|
|
633
|
+
let privateState;
|
|
634
|
+
beforeEach(async () => {
|
|
635
|
+
const initResult = await engine.processCommand(null, null, {
|
|
636
|
+
id: 'init',
|
|
637
|
+
type: 'INIT_SESSION_STATE',
|
|
638
|
+
payload: { betAmount: 1.0 },
|
|
639
|
+
});
|
|
640
|
+
publicState = initResult.publicState;
|
|
641
|
+
privateState = initResult.privateState;
|
|
642
|
+
});
|
|
643
|
+
it('should handle get symbols command', async () => {
|
|
644
|
+
const result = await engine.processCommand(publicState, privateState, {
|
|
645
|
+
id: 'symbols',
|
|
646
|
+
type: 'GET_SYMBOLS',
|
|
647
|
+
payload: {},
|
|
648
|
+
});
|
|
649
|
+
expect(result).toBeDefined();
|
|
650
|
+
});
|
|
651
|
+
});
|
|
652
|
+
// ============================================================
|
|
653
|
+
// STATE PERSISTENCE
|
|
654
|
+
// ============================================================
|
|
655
|
+
describe('State Persistence', () => {
|
|
656
|
+
let publicState;
|
|
657
|
+
let privateState;
|
|
658
|
+
beforeEach(async () => {
|
|
659
|
+
const initResult = await engine.processCommand(null, null, {
|
|
660
|
+
id: 'init',
|
|
661
|
+
type: 'INIT_SESSION_STATE',
|
|
662
|
+
payload: { betAmount: 1.0 },
|
|
663
|
+
});
|
|
664
|
+
publicState = initResult.publicState;
|
|
665
|
+
privateState = initResult.privateState;
|
|
666
|
+
});
|
|
667
|
+
it('should serialize and deserialize state correctly', async () => {
|
|
668
|
+
// Spin to have meaningful state
|
|
669
|
+
const spin = await engine.processCommand(publicState, privateState, {
|
|
670
|
+
id: 'spin',
|
|
671
|
+
type: 'SPIN',
|
|
672
|
+
payload: {
|
|
673
|
+
sessionId: 'test',
|
|
674
|
+
betAmount: 1.0,
|
|
675
|
+
},
|
|
676
|
+
});
|
|
677
|
+
// Serialize
|
|
678
|
+
const serializedPublic = JSON.stringify(spin.publicState);
|
|
679
|
+
const serializedPrivate = JSON.stringify(spin.privateState);
|
|
680
|
+
// Deserialize
|
|
681
|
+
const restoredPublic = JSON.parse(serializedPublic);
|
|
682
|
+
const restoredPrivate = JSON.parse(serializedPrivate);
|
|
683
|
+
// Verify serialization round-trip preserves data
|
|
684
|
+
expect(restoredPublic).toEqual(spin.publicState);
|
|
685
|
+
expect(restoredPrivate).toEqual(spin.privateState);
|
|
686
|
+
// Continue with restored state only if base game spin is expected
|
|
687
|
+
// (DummyRng may trigger bonus meters, changing nextSpinType)
|
|
688
|
+
if (restoredPrivate.nextSpinType === 'BASE_GAME_SPIN') {
|
|
689
|
+
const result = await engine.processCommand(restoredPublic, restoredPrivate, {
|
|
690
|
+
id: 'spin-2',
|
|
691
|
+
type: 'SPIN',
|
|
692
|
+
payload: {
|
|
693
|
+
sessionId: 'test',
|
|
694
|
+
betAmount: 1.0,
|
|
695
|
+
},
|
|
696
|
+
});
|
|
697
|
+
expect(result.success).toBe(true);
|
|
698
|
+
}
|
|
699
|
+
});
|
|
700
|
+
it('should preserve bonus meters across reconnection', async () => {
|
|
701
|
+
// Spin to accumulate meter
|
|
702
|
+
const spin = await engine.processCommand(publicState, privateState, {
|
|
703
|
+
id: 'spin',
|
|
704
|
+
type: 'SPIN',
|
|
705
|
+
payload: { sessionId: 'test', betAmount: 1.0 },
|
|
706
|
+
});
|
|
707
|
+
// Serialize and restore
|
|
708
|
+
const serialized = JSON.stringify(spin.publicState);
|
|
709
|
+
const restored = JSON.parse(serialized);
|
|
710
|
+
expect(restored.bonusMeters).toBeDefined();
|
|
711
|
+
});
|
|
712
|
+
it('should maintain state across multiple operations', async () => {
|
|
713
|
+
let currentPublic = publicState;
|
|
714
|
+
let currentPrivate = privateState;
|
|
715
|
+
// Multiple spins
|
|
716
|
+
for (let i = 0; i < 5; i++) {
|
|
717
|
+
const result = await engine.processCommand(currentPublic, currentPrivate, {
|
|
718
|
+
id: `spin-${i}`,
|
|
719
|
+
type: 'SPIN',
|
|
720
|
+
payload: {
|
|
721
|
+
sessionId: 'test',
|
|
722
|
+
betAmount: 1.0,
|
|
723
|
+
},
|
|
724
|
+
});
|
|
725
|
+
expect(result.success).toBe(true);
|
|
726
|
+
currentPublic = result.publicState;
|
|
727
|
+
currentPrivate = result.privateState;
|
|
728
|
+
}
|
|
729
|
+
// Trigger various bonuses
|
|
730
|
+
for (const bonusType of ['bonusGame1', 'bonusGame2']) {
|
|
731
|
+
const bonus = await engine.processCommand(currentPublic, currentPrivate, {
|
|
732
|
+
id: `bonus-${bonusType}`,
|
|
733
|
+
type: 'DEBUG_TRIGGER_BONUS',
|
|
734
|
+
payload: {
|
|
735
|
+
sessionId: 'test',
|
|
736
|
+
bonusType,
|
|
737
|
+
betAmount: 1.0,
|
|
738
|
+
},
|
|
739
|
+
});
|
|
740
|
+
expect(bonus.success).toBe(true);
|
|
741
|
+
currentPublic = bonus.publicState;
|
|
742
|
+
currentPrivate = bonus.privateState;
|
|
743
|
+
}
|
|
744
|
+
});
|
|
745
|
+
});
|
|
746
|
+
});
|
|
747
|
+
//# sourceMappingURL=comprehensive.test.js.map
|