dubs-server 1.0.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/.claude/settings.local.json +280 -0
- package/CLAUDE.md +46 -0
- package/CONNECT4_PRODUCTION_DEPLOY.md +155 -0
- package/CURRENT_SESSION.md +171 -0
- package/CURRENT_SESSION_DRAW.md +516 -0
- package/MARCH_MADNESS_SURVIVOR.md +254 -0
- package/PANDA.md +166 -0
- package/Procfile +4 -0
- package/README.md +476 -0
- package/controllers/livescoresController.js +376 -0
- package/controllers/pickemController.js +554 -0
- package/controllers/survivorAdminController.js +887 -0
- package/controllers/survivorController.js +623 -0
- package/cron/oracleMonitor.js +77 -0
- package/cron/pickemOracleMonitor.js +73 -0
- package/data/jackpot-history.json +952 -0
- package/data/ncaaTeams.js +406 -0
- package/documentation/API_SECURITY_GUIDE.md +327 -0
- package/documentation/ARCADE_API.md +593 -0
- package/documentation/ARCADE_IMPLEMENTATION_SUMMARY.md +399 -0
- package/documentation/ARCADE_QUICKSTART.md +242 -0
- package/documentation/AUTOMATIC_MODE_ORACLE.md +321 -0
- package/documentation/BUG_FIX_COHORT_DATE_DISPLAY.md +171 -0
- package/documentation/CLAIM_MIGRATION_INSTRUCTIONS.md +52 -0
- package/documentation/CLAIM_STATUS_FIX.md +67 -0
- package/documentation/CLI_TOOL_GUIDE.md +372 -0
- package/documentation/COHORT_RETENTION_ANALYSIS.md +295 -0
- package/documentation/COHORT_RETENTION_IMPLEMENTATION_COMPLETE.md +461 -0
- package/documentation/COHORT_RETENTION_SUMMARY.md +204 -0
- package/documentation/COMPLETE_PROJECT_SUMMARY.md +490 -0
- package/documentation/DATABASE_QUERIES.md +269 -0
- package/documentation/DATABASE_RETENTION_POLICY.md +390 -0
- package/documentation/DATABASE_SETUP_GUIDE.md +361 -0
- package/documentation/DATABASE_SETUP_SUMMARY.md +247 -0
- package/documentation/DEMO_API_CURL_COMMANDS.md +656 -0
- package/documentation/DEPLOYMENT_SUMMARY.txt +100 -0
- package/documentation/DUPLICATE_NOTIFICATIONS_FIXED.md +201 -0
- package/documentation/EXCHANGE_RATES_INTEGRATION.md +371 -0
- package/documentation/FINAL_API_PROTECTION_TABLE.md +175 -0
- package/documentation/GAME_START_NOTIFICATIONS_DEPLOYMENT.md +256 -0
- package/documentation/GAME_START_NOTIFICATIONS_INTEGRATION.md +275 -0
- package/documentation/HEROKU_DEPLOYMENT.md +134 -0
- package/documentation/HEROKU_SCHEDULER_SETUP.md +271 -0
- package/documentation/JACKPOT_API.md +521 -0
- package/documentation/JACKPOT_DEPLOYMENT_GUIDE.md +362 -0
- package/documentation/JWT_IMPLEMENTATION_SUMMARY.md +373 -0
- package/documentation/JWT_QUICK_SETUP.md +268 -0
- package/documentation/JWT_TESTING_GUIDE.md +404 -0
- package/documentation/KEEPER_RECOVERY_GUIDE.md +381 -0
- package/documentation/KEEPER_SETUP.md +206 -0
- package/documentation/KEEPER_STATE_MACHINE.md +423 -0
- package/documentation/LATEST_PRODUCTION_SETUP.md +387 -0
- package/documentation/LOCAL_VOTING_TEST.md +279 -0
- package/documentation/ORACLE_FIXES_SUMMARY.md +188 -0
- package/documentation/ORACLE_POSTGRESQL_UPDATE.md +202 -0
- package/documentation/PAYMENT_DEPLOYMENT.md +209 -0
- package/documentation/PNL_TRACKING_SETUP.md +189 -0
- package/documentation/PREVENTING_LOCKUP_ERRORS.md +472 -0
- package/documentation/PRODUCTION_READY_SUMMARY.md +227 -0
- package/documentation/PUBLIC_VS_PRIVATE_ENDPOINTS.md +278 -0
- package/documentation/QUICK_AUTH_SETUP.md +99 -0
- package/documentation/QUICK_DEPLOY.md +224 -0
- package/documentation/QUICK_FIX.md +114 -0
- package/documentation/QUICK_START.md +152 -0
- package/documentation/REFEREE_MODE_GUIDE.md +392 -0
- package/documentation/RETENTION_CORE_ACTION_UPDATE.md +313 -0
- package/documentation/RETENTION_UPDATE_SUMMARY.md +108 -0
- package/documentation/RUN_MIGRATION_NOW.md +39 -0
- package/documentation/SCRIPTS_UPDATE_SUMMARY.md +251 -0
- package/documentation/SETUP_GUIDE.md +184 -0
- package/documentation/STATE_MACHINE_IMPLEMENTATION.md +250 -0
- package/documentation/TELEGRAM_NOTIFICATIONS_DIAGNOSIS.md +361 -0
- package/documentation/UNIFIED_ARCHITECTURE.md +231 -0
- package/documentation/VOTING_DEPLOYMENT_SUMMARY.md +392 -0
- package/documentation/WEBSOCKET_ARCHITECTURE.md +881 -0
- package/documentation/WHAT_WE_BUILT_TODAY.md +369 -0
- package/documentation/latest/LATEST_PRODUCTION_SETUP.md +865 -0
- package/ecosystem.config.js +65 -0
- package/env.template +125 -0
- package/middleware/apiKeyAuth.js +136 -0
- package/middleware/authenticate.js +214 -0
- package/middleware/developerUserAuth.js +76 -0
- package/middleware/socketAuth.js +69 -0
- package/package.json +49 -0
- package/postman/Dubs-API-v1-With-Voting.postman_collection.json +555 -0
- package/postman/Dubs-API-v1.postman_collection.json +205 -0
- package/postman/Dubs_Developer_API.postman_collection.json +662 -0
- package/postman/QUICKSTART.md +118 -0
- package/postman/QUICK_REFERENCE.md +246 -0
- package/postman/README.md +71 -0
- package/postman/VOTING_API_GUIDE.md +426 -0
- package/refactor/Animations.md +148 -0
- package/refactor/Chat.md +252 -0
- package/routes/actionsRoutes.js +699 -0
- package/routes/adminRoutes.js +370 -0
- package/routes/analyticsRoutes.js +1262 -0
- package/routes/arcadeRoutes.js +557 -0
- package/routes/authRoutes.js +2310 -0
- package/routes/avatarRoutes.js +85 -0
- package/routes/botRoutes.js +211 -0
- package/routes/chatRoutes.js +377 -0
- package/routes/cryptoPriceRoutes.js +105 -0
- package/routes/developerRoutes.js +4201 -0
- package/routes/deviceRoutes.js +214 -0
- package/routes/dmRoutes.js +167 -0
- package/routes/esportsRoutes.js +806 -0
- package/routes/exchangeRateRoutes.js +233 -0
- package/routes/gamesRoutes.js +3028 -0
- package/routes/jackpotRoutes.js +754 -0
- package/routes/keeperMonitoringRoutes.js +156 -0
- package/routes/keeperWebhookRoutes.js +466 -0
- package/routes/livescoresRoutes.js +31 -0
- package/routes/pickemAdminRoutes.js +199 -0
- package/routes/pickemRoutes.js +231 -0
- package/routes/playerStatsRoutes.js +147 -0
- package/routes/portfolioRoutes.js +217 -0
- package/routes/promoRoutes.js +418 -0
- package/routes/referralEarningsRoutes.js +392 -0
- package/routes/socialRoutes.js +459 -0
- package/routes/sportsRoutes.js +1271 -0
- package/routes/survivorAdminRoutes.js +345 -0
- package/routes/survivorRoutes.js +756 -0
- package/routes/uploadRoutes.js +256 -0
- package/routes/userProfileRoutes.js +244 -0
- package/routes/whatsNewRoutes.js +331 -0
- package/scripts/.claude/settings.local.json +15 -0
- package/scripts/README.md +170 -0
- package/scripts/RESTART_EVERYTHING.sh +104 -0
- package/scripts/add-claim-columns.sql +48 -0
- package/scripts/add-crypto-prices-cache.sql +27 -0
- package/scripts/add-exchange-rates-cache.sql +40 -0
- package/scripts/add-game-invite-column.sql +23 -0
- package/scripts/add-game-invite-notification.sql +33 -0
- package/scripts/add-game-invite-telegram-pref.sql +16 -0
- package/scripts/add-game-joined-notification.sql +16 -0
- package/scripts/add-game-joined-pref.js +40 -0
- package/scripts/add-game-joined-preference.sql +6 -0
- package/scripts/add-game-start-notifications.sql +41 -0
- package/scripts/add-notification-flags-to-games.sql +55 -0
- package/scripts/add-pending-game-dismissals.sql +19 -0
- package/scripts/add-preferred-currency.sql +34 -0
- package/scripts/add-winner-columns.js +61 -0
- package/scripts/add_mention_system.sql +53 -0
- package/scripts/add_payment_system.sql +96 -0
- package/scripts/add_sports_event_id_column.sql +22 -0
- package/scripts/analyze-cohort-data-heroku.js +276 -0
- package/scripts/analyze-cohort-data.js +295 -0
- package/scripts/analyze-prod-cohorts.sh +10 -0
- package/scripts/backfill-matchup-images.js +245 -0
- package/scripts/backfill-missing-signatures.js +175 -0
- package/scripts/backfill-referral-earnings.js +202 -0
- package/scripts/check-chat-schema.js +130 -0
- package/scripts/check-db.sh +14 -0
- package/scripts/check_oracle_in_game.js +54 -0
- package/scripts/cleanup-database.js +193 -0
- package/scripts/clear-notification-cache.js +85 -0
- package/scripts/convert-mnemonic.js +50 -0
- package/scripts/create-users-table.sql +44 -0
- package/scripts/debug-cohort-counts.js +248 -0
- package/scripts/debug-winner-calc.js +84 -0
- package/scripts/deploy-payment-system.sh +118 -0
- package/scripts/deploy-to-heroku.sh +63 -0
- package/scripts/diagnose-locked-round.js +143 -0
- package/scripts/dubs-cli.js +720 -0
- package/scripts/dump-account.js +65 -0
- package/scripts/find-vrf-offset.js +48 -0
- package/scripts/fix-chat-notifications-constraint.sql +122 -0
- package/scripts/fix-claim-columns.js +124 -0
- package/scripts/fix-constraint-now.js +44 -0
- package/scripts/fix-lock-timestamps.js +96 -0
- package/scripts/fix-locked-round.sh +126 -0
- package/scripts/fix-missing-badges.sql +91 -0
- package/scripts/fix-payment-notifications.sql +41 -0
- package/scripts/force-new-round.js +55 -0
- package/scripts/force-resolve-and-claim.js +278 -0
- package/scripts/important/README.md +115 -0
- package/scripts/important/authority-force-lock.js +197 -0
- package/scripts/important/authority-resolve-game.js +267 -0
- package/scripts/important/check-game-status.js +373 -0
- package/scripts/important/list-pending-games-by-version.js +270 -0
- package/scripts/important/reconcile-v1-v2-payouts.js +270 -0
- package/scripts/initialize-jackpot.js +111 -0
- package/scripts/jackpot/.claude/settings.local.json +10 -0
- package/scripts/jackpot/force-reset.js +84 -0
- package/scripts/jackpot/initialize-mainnet.js +100 -0
- package/scripts/jackpot/keeper.js +742 -0
- package/scripts/jackpot/status.js +107 -0
- package/scripts/jackpot/update-round-duration.js +143 -0
- package/scripts/keeper-bot.js +112 -0
- package/scripts/list-pending-games.js +131 -0
- package/scripts/migrate-chat-v2.js +127 -0
- package/scripts/migrate-chat-winners.js +84 -0
- package/scripts/migrate-chat.sh +17 -0
- package/scripts/migrate-game-invite.js +83 -0
- package/scripts/migrate-heroku-game-notifications.sh +159 -0
- package/scripts/migrations/001_analytics_tables.sql +422 -0
- package/scripts/migrations/002_add_matchup_image_url.sql +14 -0
- package/scripts/migrations/003_referral_earnings.sql +208 -0
- package/scripts/migrations/004_add_whats_new_notification_type.sql +62 -0
- package/scripts/migrations/005_add_connect4_your_turn_notification.sql +61 -0
- package/scripts/migrations/005_push_notifications.sql +55 -0
- package/scripts/migrations/006_add_draw_team_players.sql +28 -0
- package/scripts/migrations/006_add_game_cancelled_notification.sql +62 -0
- package/scripts/migrations/007_add_gif_url.sql +8 -0
- package/scripts/migrations/008_add_connect4_columns.sql +139 -0
- package/scripts/migrations/008_add_pool_tracking.sql +22 -0
- package/scripts/migrations/009_create_survivor_pool_tables.sql +174 -0
- package/scripts/migrations/010_add_survivor_pool_outcome.sql +28 -0
- package/scripts/migrations/011_create_developer_tables.sql +67 -0
- package/scripts/migrations/011_fix_keeper_tables.sql +85 -0
- package/scripts/migrations/012_create_developer_webhooks.sql +31 -0
- package/scripts/migrations/013_add_network_mode.sql +18 -0
- package/scripts/migrations/014_create_developer_app_users.sql +19 -0
- package/scripts/migrations/015_add_ui_config.sql +4 -0
- package/scripts/migrations/016_add_resolution_secret.sql +4 -0
- package/scripts/migrations/017_add_external_game_id.sql +3 -0
- package/scripts/migrations/018_create_pickem_tables.sql +115 -0
- package/scripts/migrations/019_expo_push_tokens.sql +19 -0
- package/scripts/migrations/create_whats_new_tables.sql +88 -0
- package/scripts/migrations/drop_live_games_tables.sql +34 -0
- package/scripts/open-jackpot-round.js +85 -0
- package/scripts/purge-all-data.sh +329 -0
- package/scripts/purge-all-data.sql +142 -0
- package/scripts/purge-heroku-data.sh +149 -0
- package/scripts/purge-heroku-data.sql +62 -0
- package/scripts/rebuild-heroku-database.sh +113 -0
- package/scripts/recover-funds.js +357 -0
- package/scripts/regenerate-epl-images.js +278 -0
- package/scripts/resize-s3-matchup-images.js +374 -0
- package/scripts/resolve-direct.js +88 -0
- package/scripts/resolve-mock-game.js +124 -0
- package/scripts/resolve-pickem-game.js +55 -0
- package/scripts/resolve-round-manual.js +83 -0
- package/scripts/resolve-stuck-game.js +382 -0
- package/scripts/resolve-stuck-round.js +42 -0
- package/scripts/run-connect4-migration.sh +16 -0
- package/scripts/run-mention-migration.sh +32 -0
- package/scripts/run-payment-migration.sh +51 -0
- package/scripts/run-preferred-currency-migration.sh +31 -0
- package/scripts/run-referral-earnings-migration.sh +32 -0
- package/scripts/run-survivor-outcome-migration.sh +16 -0
- package/scripts/seed-test-users.js +346 -0
- package/scripts/setup-auth-tables.js +78 -0
- package/scripts/setup-complete-database.sql +992 -0
- package/scripts/setup-database-fresh.sh +359 -0
- package/scripts/setup-heroku-keeper.sh +48 -0
- package/scripts/setup-keeper-database.js +83 -0
- package/scripts/setup-keeper-state-db.sql +110 -0
- package/scripts/setup-oracle.sh +39 -0
- package/scripts/setup-pnl-tracking.js +111 -0
- package/scripts/start-devnet.sh +14 -0
- package/scripts/test-arcade-devnet.sh +160 -0
- package/scripts/test-arcade-match.sh +109 -0
- package/scripts/test-automatic-mode.sh +239 -0
- package/scripts/test-connect4-cancel-claim.js +370 -0
- package/scripts/test-connect4-e2e.js +369 -0
- package/scripts/test-connect4-resolve.js +369 -0
- package/scripts/test-game-state-endpoint.js +136 -0
- package/scripts/test-invite-notification.js +86 -0
- package/scripts/test-jackpot-api.sh +71 -0
- package/scripts/test-poll-confirmation.js +267 -0
- package/scripts/test-resolve-game.js +271 -0
- package/scripts/test-resolve-signature.js +223 -0
- package/scripts/test-signature-preservation.js +124 -0
- package/scripts/test-state-machine.js +291 -0
- package/scripts/test-webhook-receiver.js +60 -0
- package/scripts/update-notification-constraint.js +52 -0
- package/scripts/verify-account-layout.js +145 -0
- package/scripts/verify-winner-algorithm.js +278 -0
- package/server.js +5259 -0
- package/services/arcadeMatchService.js +763 -0
- package/services/automaticGameOracle.js +1596 -0
- package/services/chatService.js +1612 -0
- package/services/connect4GameService.js +1049 -0
- package/services/connect4NotificationService.js +374 -0
- package/services/cryptoPriceService.js +223 -0
- package/services/customGameResolver.js +260 -0
- package/services/db.js +79 -0
- package/services/directMessageService.js +389 -0
- package/services/discordNotifications.js +160 -0
- package/services/exchangeRateService.js +289 -0
- package/services/expoPushService.js +314 -0
- package/services/gamesCacheService.js +539 -0
- package/services/jackpotHistory.js +331 -0
- package/services/jackpotService.js +856 -0
- package/services/keeperStateService.js +355 -0
- package/services/matchupImageService.js +591 -0
- package/services/notificationCacheService.js +407 -0
- package/services/pickemOracle.js +440 -0
- package/services/playerStatsService.js +389 -0
- package/services/portfolioService.js +555 -0
- package/services/promoService.js +757 -0
- package/services/promoTreasuryService.js +239 -0
- package/services/pushNotifications.js +353 -0
- package/services/redisService.js +422 -0
- package/services/referralEarningsService.js +728 -0
- package/services/s3Service.js +396 -0
- package/services/socialService.js +1202 -0
- package/services/survivorOracle.js +469 -0
- package/services/survivorSimulator.js +475 -0
- package/services/telegramNotifications.js +461 -0
- package/services/userProfileStatsService.js +1185 -0
- package/services/whatsNewService.js +388 -0
- package/utils/urlHelper.js +95 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Test script to verify resolveSignature is being saved correctly
|
|
4
|
+
*
|
|
5
|
+
* Tests:
|
|
6
|
+
* 1. games.claim_signature is saved
|
|
7
|
+
* 2. user_game_refs.claim_signature is saved for winners
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* node scripts/test-resolve-signature.js
|
|
11
|
+
*
|
|
12
|
+
* Environment:
|
|
13
|
+
* DATABASE_URL - PostgreSQL connection string
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
require('dotenv').config();
|
|
17
|
+
const { Pool } = require('pg');
|
|
18
|
+
|
|
19
|
+
const pool = new Pool({
|
|
20
|
+
connectionString: process.env.DATABASE_URL,
|
|
21
|
+
ssl: process.env.DATABASE_URL?.includes('amazonaws.com') ? { rejectUnauthorized: false } : false
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const TEST_SIGNATURE = 'TEST_SIG_' + Date.now() + '_ABCDEFGHIJKLMNOP';
|
|
25
|
+
|
|
26
|
+
async function findTestableGame() {
|
|
27
|
+
// Find an unresolved game with players on both sides
|
|
28
|
+
const result = await pool.query(`
|
|
29
|
+
SELECT
|
|
30
|
+
g.game_id,
|
|
31
|
+
g.title,
|
|
32
|
+
g.home_team_players,
|
|
33
|
+
g.away_team_players,
|
|
34
|
+
g.is_resolved,
|
|
35
|
+
g.claim_signature
|
|
36
|
+
FROM games g
|
|
37
|
+
WHERE g.is_resolved = false
|
|
38
|
+
AND array_length(g.home_team_players, 1) > 0
|
|
39
|
+
AND array_length(g.away_team_players, 1) > 0
|
|
40
|
+
ORDER BY g.created_at DESC
|
|
41
|
+
LIMIT 1
|
|
42
|
+
`);
|
|
43
|
+
|
|
44
|
+
if (result.rows.length === 0) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return result.rows[0];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function testResolveEndpoint(gameId) {
|
|
52
|
+
const axios = require('axios');
|
|
53
|
+
|
|
54
|
+
const baseUrl = process.env.DUBS_SERVER_URL || 'http://localhost:3001';
|
|
55
|
+
const url = `${baseUrl}/api/games/${gameId}/resolve`;
|
|
56
|
+
|
|
57
|
+
console.log(`\n📡 POST ${url}`);
|
|
58
|
+
console.log(` Body: { winner: 'home', homeScore: 99, awayScore: 98, resolveSignature: '${TEST_SIGNATURE.slice(0, 30)}...' }`);
|
|
59
|
+
|
|
60
|
+
const response = await axios.post(url, {
|
|
61
|
+
winner: 'home',
|
|
62
|
+
homeScore: 99,
|
|
63
|
+
awayScore: 98,
|
|
64
|
+
resolvedAt: new Date().toISOString(),
|
|
65
|
+
resolvedBy: 'test-script',
|
|
66
|
+
resolveSignature: TEST_SIGNATURE
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
return response.data;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function verifyGameSignature(gameId) {
|
|
73
|
+
const result = await pool.query(`
|
|
74
|
+
SELECT game_id, claim_signature, is_resolved
|
|
75
|
+
FROM games
|
|
76
|
+
WHERE game_id = $1
|
|
77
|
+
`, [gameId]);
|
|
78
|
+
|
|
79
|
+
if (result.rows.length === 0) {
|
|
80
|
+
return { success: false, error: 'Game not found' };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const game = result.rows[0];
|
|
84
|
+
return {
|
|
85
|
+
success: game.claim_signature === TEST_SIGNATURE,
|
|
86
|
+
claim_signature: game.claim_signature,
|
|
87
|
+
is_resolved: game.is_resolved,
|
|
88
|
+
expected: TEST_SIGNATURE
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function verifyWinnersSignature(gameId) {
|
|
93
|
+
const result = await pool.query(`
|
|
94
|
+
SELECT wallet_address, claim_signature
|
|
95
|
+
FROM user_game_refs
|
|
96
|
+
WHERE game_id = $1
|
|
97
|
+
AND claim_signature IS NOT NULL
|
|
98
|
+
`, [gameId]);
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
winnersUpdated: result.rows.length,
|
|
102
|
+
winners: result.rows.map(r => ({
|
|
103
|
+
wallet: r.wallet_address.slice(0, 8) + '...',
|
|
104
|
+
signature: r.claim_signature?.slice(0, 30) + '...'
|
|
105
|
+
}))
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function revertTestGame(gameId, originalState) {
|
|
110
|
+
console.log('\n🔄 Reverting game to original state...');
|
|
111
|
+
|
|
112
|
+
await pool.query(`
|
|
113
|
+
UPDATE games
|
|
114
|
+
SET
|
|
115
|
+
is_resolved = $2,
|
|
116
|
+
claim_signature = $3,
|
|
117
|
+
automatic_status = 'pending',
|
|
118
|
+
sports_event = sports_event - 'finalScore'
|
|
119
|
+
WHERE game_id = $1
|
|
120
|
+
`, [gameId, originalState.is_resolved, originalState.claim_signature]);
|
|
121
|
+
|
|
122
|
+
await pool.query(`
|
|
123
|
+
UPDATE user_game_refs
|
|
124
|
+
SET claim_signature = NULL
|
|
125
|
+
WHERE game_id = $1 AND claim_signature = $2
|
|
126
|
+
`, [gameId, TEST_SIGNATURE]);
|
|
127
|
+
|
|
128
|
+
console.log(' ✅ Reverted');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function main() {
|
|
132
|
+
console.log('═══════════════════════════════════════════════════════════');
|
|
133
|
+
console.log(' Test: resolveSignature saving to database');
|
|
134
|
+
console.log('═══════════════════════════════════════════════════════════');
|
|
135
|
+
console.log(`\nTest signature: ${TEST_SIGNATURE}`);
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
// Step 1: Find a testable game
|
|
139
|
+
console.log('\n📋 Step 1: Finding an unresolved game with players...');
|
|
140
|
+
const game = await findTestableGame();
|
|
141
|
+
|
|
142
|
+
if (!game) {
|
|
143
|
+
console.log(' ❌ No testable game found (need unresolved game with home and away players)');
|
|
144
|
+
console.log(' You may need to create a test game first.');
|
|
145
|
+
process.exit(1);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
console.log(` ✅ Found: ${game.game_id}`);
|
|
149
|
+
console.log(` Title: ${game.title}`);
|
|
150
|
+
console.log(` Home players: ${game.home_team_players?.length || 0}`);
|
|
151
|
+
console.log(` Away players: ${game.away_team_players?.length || 0}`);
|
|
152
|
+
console.log(` Current claim_signature: ${game.claim_signature || 'NULL'}`);
|
|
153
|
+
|
|
154
|
+
const originalState = {
|
|
155
|
+
is_resolved: game.is_resolved,
|
|
156
|
+
claim_signature: game.claim_signature
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
// Step 2: Call resolve endpoint
|
|
160
|
+
console.log('\n📋 Step 2: Calling resolve endpoint with test signature...');
|
|
161
|
+
try {
|
|
162
|
+
const resolveResult = await testResolveEndpoint(game.game_id);
|
|
163
|
+
console.log(` ✅ Response: ${resolveResult.success ? 'success' : 'failed'}`);
|
|
164
|
+
} catch (err) {
|
|
165
|
+
console.log(` ❌ Request failed: ${err.message}`);
|
|
166
|
+
if (err.response) {
|
|
167
|
+
console.log(` Status: ${err.response.status}`);
|
|
168
|
+
console.log(` Data: ${JSON.stringify(err.response.data)}`);
|
|
169
|
+
}
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Step 3: Verify games.claim_signature
|
|
174
|
+
console.log('\n📋 Step 3: Verifying games.claim_signature...');
|
|
175
|
+
const gameVerify = await verifyGameSignature(game.game_id);
|
|
176
|
+
|
|
177
|
+
if (gameVerify.success) {
|
|
178
|
+
console.log(` ✅ PASS: games.claim_signature saved correctly`);
|
|
179
|
+
console.log(` Saved: ${gameVerify.claim_signature?.slice(0, 40)}...`);
|
|
180
|
+
} else {
|
|
181
|
+
console.log(` ❌ FAIL: games.claim_signature NOT saved correctly`);
|
|
182
|
+
console.log(` Expected: ${gameVerify.expected?.slice(0, 40)}...`);
|
|
183
|
+
console.log(` Got: ${gameVerify.claim_signature || 'NULL'}`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Step 4: Verify user_game_refs.claim_signature for winners
|
|
187
|
+
console.log('\n📋 Step 4: Verifying user_game_refs.claim_signature for winners...');
|
|
188
|
+
const winnersVerify = await verifyWinnersSignature(game.game_id);
|
|
189
|
+
|
|
190
|
+
if (winnersVerify.winnersUpdated > 0) {
|
|
191
|
+
console.log(` ✅ PASS: ${winnersVerify.winnersUpdated} winner(s) updated with claim_signature`);
|
|
192
|
+
winnersVerify.winners.forEach(w => {
|
|
193
|
+
console.log(` - ${w.wallet}: ${w.signature}`);
|
|
194
|
+
});
|
|
195
|
+
} else {
|
|
196
|
+
console.log(` ⚠️ WARNING: No winners updated with claim_signature`);
|
|
197
|
+
console.log(` This could be a bug, or there were no home players (we set winner='home')`);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Step 5: Revert
|
|
201
|
+
console.log('\n📋 Step 5: Reverting test changes...');
|
|
202
|
+
await revertTestGame(game.game_id, originalState);
|
|
203
|
+
|
|
204
|
+
// Summary
|
|
205
|
+
console.log('\n═══════════════════════════════════════════════════════════');
|
|
206
|
+
console.log(' TEST RESULTS');
|
|
207
|
+
console.log('═══════════════════════════════════════════════════════════');
|
|
208
|
+
console.log(` games.claim_signature: ${gameVerify.success ? '✅ PASS' : '❌ FAIL'}`);
|
|
209
|
+
console.log(` user_game_refs signatures: ${winnersVerify.winnersUpdated > 0 ? '✅ PASS' : '⚠️ CHECK'}`);
|
|
210
|
+
console.log('═══════════════════════════════════════════════════════════\n');
|
|
211
|
+
|
|
212
|
+
process.exit(gameVerify.success ? 0 : 1);
|
|
213
|
+
|
|
214
|
+
} catch (error) {
|
|
215
|
+
console.error('\n❌ Test failed with error:', error.message);
|
|
216
|
+
console.error(error.stack);
|
|
217
|
+
process.exit(1);
|
|
218
|
+
} finally {
|
|
219
|
+
await pool.end();
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
main();
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Test script to verify that my_signature is preserved on upsert
|
|
4
|
+
*
|
|
5
|
+
* Tests the fix for: ON CONFLICT should use COALESCE to preserve existing signatures
|
|
6
|
+
*
|
|
7
|
+
* Run with: node scripts/test-signature-preservation.js
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { Pool } = require('pg');
|
|
11
|
+
require('dotenv').config();
|
|
12
|
+
|
|
13
|
+
const pool = new Pool({
|
|
14
|
+
connectionString: process.env.DATABASE_URL,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
async function runTest() {
|
|
18
|
+
const testWallet = 'TEST_WALLET_' + Date.now();
|
|
19
|
+
const testGameId = 'c4-test-' + Date.now();
|
|
20
|
+
const testSignature = 'TEST_SIGNATURE_' + Date.now();
|
|
21
|
+
const testExplorerUrl = 'https://explorer.solana.com/tx/' + testSignature;
|
|
22
|
+
|
|
23
|
+
console.log('🧪 Testing signature preservation fix...\n');
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
// Step 1: Insert a record WITH signature
|
|
27
|
+
console.log('1️⃣ Inserting record with signature...');
|
|
28
|
+
await pool.query(`
|
|
29
|
+
INSERT INTO user_game_refs (
|
|
30
|
+
wallet_address, game_id, role, joined_at, team_choice,
|
|
31
|
+
my_signature, my_explorer_url, status
|
|
32
|
+
) VALUES ($1, $2, $3, NOW(), $4, $5, $6, $7)
|
|
33
|
+
`, [testWallet, testGameId, 'player', 'away', testSignature, testExplorerUrl, 'active']);
|
|
34
|
+
|
|
35
|
+
// Verify it was inserted correctly
|
|
36
|
+
const afterInsert = await pool.query(
|
|
37
|
+
'SELECT my_signature, my_explorer_url FROM user_game_refs WHERE wallet_address = $1 AND game_id = $2',
|
|
38
|
+
[testWallet, testGameId]
|
|
39
|
+
);
|
|
40
|
+
console.log(' Inserted signature:', afterInsert.rows[0].my_signature);
|
|
41
|
+
console.log(' ✅ Signature saved correctly\n');
|
|
42
|
+
|
|
43
|
+
// Step 2: Simulate an upsert WITHOUT signature (like error 6030 handler)
|
|
44
|
+
console.log('2️⃣ Upserting WITHOUT signature (simulating error 6030 handler)...');
|
|
45
|
+
await pool.query(`
|
|
46
|
+
INSERT INTO user_game_refs (
|
|
47
|
+
wallet_address, game_id, role, joined_at, team_choice,
|
|
48
|
+
my_signature, my_explorer_url, status, wallet_type
|
|
49
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
|
50
|
+
ON CONFLICT (wallet_address, game_id)
|
|
51
|
+
DO UPDATE SET
|
|
52
|
+
team_choice = EXCLUDED.team_choice,
|
|
53
|
+
my_signature = COALESCE(EXCLUDED.my_signature, user_game_refs.my_signature),
|
|
54
|
+
my_explorer_url = COALESCE(EXCLUDED.my_explorer_url, user_game_refs.my_explorer_url),
|
|
55
|
+
status = EXCLUDED.status
|
|
56
|
+
`, [testWallet, testGameId, 'player', new Date().toISOString(), 'away', null, null, 'updated', null]);
|
|
57
|
+
|
|
58
|
+
// Verify signature was preserved
|
|
59
|
+
const afterUpsert = await pool.query(
|
|
60
|
+
'SELECT my_signature, my_explorer_url, status FROM user_game_refs WHERE wallet_address = $1 AND game_id = $2',
|
|
61
|
+
[testWallet, testGameId]
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
console.log(' After upsert signature:', afterUpsert.rows[0].my_signature);
|
|
65
|
+
console.log(' After upsert status:', afterUpsert.rows[0].status);
|
|
66
|
+
|
|
67
|
+
if (afterUpsert.rows[0].my_signature === testSignature) {
|
|
68
|
+
console.log(' ✅ PASS: Signature was preserved!\n');
|
|
69
|
+
} else {
|
|
70
|
+
console.log(' ❌ FAIL: Signature was overwritten with:', afterUpsert.rows[0].my_signature, '\n');
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Step 3: Verify that a NEW signature CAN overwrite (should still work)
|
|
75
|
+
console.log('3️⃣ Upserting WITH new signature (should update)...');
|
|
76
|
+
const newSignature = 'NEW_SIGNATURE_' + Date.now();
|
|
77
|
+
await pool.query(`
|
|
78
|
+
INSERT INTO user_game_refs (
|
|
79
|
+
wallet_address, game_id, role, joined_at, team_choice,
|
|
80
|
+
my_signature, my_explorer_url, status, wallet_type
|
|
81
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
|
82
|
+
ON CONFLICT (wallet_address, game_id)
|
|
83
|
+
DO UPDATE SET
|
|
84
|
+
team_choice = EXCLUDED.team_choice,
|
|
85
|
+
my_signature = COALESCE(EXCLUDED.my_signature, user_game_refs.my_signature),
|
|
86
|
+
my_explorer_url = COALESCE(EXCLUDED.my_explorer_url, user_game_refs.my_explorer_url),
|
|
87
|
+
status = EXCLUDED.status
|
|
88
|
+
`, [testWallet, testGameId, 'player', new Date().toISOString(), 'away', newSignature, 'new-url', 'active', null]);
|
|
89
|
+
|
|
90
|
+
const afterNewSig = await pool.query(
|
|
91
|
+
'SELECT my_signature FROM user_game_refs WHERE wallet_address = $1 AND game_id = $2',
|
|
92
|
+
[testWallet, testGameId]
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
if (afterNewSig.rows[0].my_signature === newSignature) {
|
|
96
|
+
console.log(' ✅ PASS: New signature was correctly saved!\n');
|
|
97
|
+
} else {
|
|
98
|
+
console.log(' ❌ FAIL: New signature was not saved\n');
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Cleanup
|
|
103
|
+
console.log('🧹 Cleaning up test data...');
|
|
104
|
+
await pool.query(
|
|
105
|
+
'DELETE FROM user_game_refs WHERE wallet_address = $1',
|
|
106
|
+
[testWallet]
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
console.log('\n✅ All tests passed! The COALESCE fix is working correctly.');
|
|
110
|
+
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.error('❌ Test error:', error);
|
|
113
|
+
// Cleanup on error
|
|
114
|
+
await pool.query(
|
|
115
|
+
'DELETE FROM user_game_refs WHERE wallet_address LIKE $1',
|
|
116
|
+
['TEST_WALLET_%']
|
|
117
|
+
);
|
|
118
|
+
process.exit(1);
|
|
119
|
+
} finally {
|
|
120
|
+
await pool.end();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
runTest();
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* State Machine Test Suite
|
|
5
|
+
*
|
|
6
|
+
* Tests that the keeper database monitoring actually helps:
|
|
7
|
+
* 1. Recovery from crashes
|
|
8
|
+
* 2. Stuck round detection
|
|
9
|
+
* 3. Retry tracking
|
|
10
|
+
* 4. Health monitoring
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const KeeperStateService = require('../services/keeperStateService');
|
|
14
|
+
|
|
15
|
+
console.log('🧪 KEEPER STATE MACHINE TEST SUITE\n');
|
|
16
|
+
console.log('='.repeat(80));
|
|
17
|
+
|
|
18
|
+
const state = new KeeperStateService();
|
|
19
|
+
|
|
20
|
+
async function runTests() {
|
|
21
|
+
try {
|
|
22
|
+
// ========================================================================
|
|
23
|
+
// TEST 1: Basic CRUD Operations
|
|
24
|
+
// ========================================================================
|
|
25
|
+
console.log('\n📝 TEST 1: Basic Database Operations');
|
|
26
|
+
console.log('-'.repeat(80));
|
|
27
|
+
|
|
28
|
+
const testRoundId = 999999;
|
|
29
|
+
|
|
30
|
+
// Create round
|
|
31
|
+
console.log('Creating test round 999999...');
|
|
32
|
+
await state.createRound(testRoundId, 'open');
|
|
33
|
+
let round = await state.getRound(testRoundId);
|
|
34
|
+
|
|
35
|
+
if (round && round.status === 'open') {
|
|
36
|
+
console.log('✅ Create: Round created with status "open"');
|
|
37
|
+
} else {
|
|
38
|
+
console.log('❌ Create: Failed');
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Update round
|
|
43
|
+
console.log('Updating to "locking"...');
|
|
44
|
+
await state.updateRound(testRoundId, {
|
|
45
|
+
status: 'locking',
|
|
46
|
+
last_attempt_at: new Date()
|
|
47
|
+
});
|
|
48
|
+
round = await state.getRound(testRoundId);
|
|
49
|
+
|
|
50
|
+
if (round && round.status === 'locking') {
|
|
51
|
+
console.log('✅ Update: Round updated to "locking"');
|
|
52
|
+
} else {
|
|
53
|
+
console.log('❌ Update: Failed');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Log action
|
|
58
|
+
console.log('Logging action...');
|
|
59
|
+
await state.logAction(testRoundId, 'lock', true, null, 'test-sig-123', 1234);
|
|
60
|
+
const actions = await state.getRoundActions(testRoundId, 10);
|
|
61
|
+
|
|
62
|
+
if (actions.length > 0 && actions[0].action === 'lock') {
|
|
63
|
+
console.log('✅ Log Action: Action logged successfully');
|
|
64
|
+
} else {
|
|
65
|
+
console.log('❌ Log Action: Failed');
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
console.log('\n✅ TEST 1 PASSED: Basic operations work');
|
|
70
|
+
|
|
71
|
+
// ========================================================================
|
|
72
|
+
// TEST 2: Stuck Round Detection
|
|
73
|
+
// ========================================================================
|
|
74
|
+
console.log('\n\n⏱️ TEST 2: Stuck Round Detection');
|
|
75
|
+
console.log('-'.repeat(80));
|
|
76
|
+
|
|
77
|
+
// Create a round that looks stuck (old timestamp, incomplete status)
|
|
78
|
+
const stuckRoundId = 999998;
|
|
79
|
+
await state.createRound(stuckRoundId, 'locking');
|
|
80
|
+
|
|
81
|
+
// Manually set old timestamp to simulate stuck round (disable trigger)
|
|
82
|
+
await state.pool.query(`ALTER TABLE keeper_rounds DISABLE TRIGGER update_keeper_rounds_updated_at`);
|
|
83
|
+
await state.pool.query(
|
|
84
|
+
`UPDATE keeper_rounds
|
|
85
|
+
SET updated_at = NOW() - INTERVAL '10 minutes',
|
|
86
|
+
last_attempt_at = NOW() - INTERVAL '10 minutes'
|
|
87
|
+
WHERE round_id = $1`,
|
|
88
|
+
[stuckRoundId]
|
|
89
|
+
);
|
|
90
|
+
await state.pool.query(`ALTER TABLE keeper_rounds ENABLE TRIGGER update_keeper_rounds_updated_at`);
|
|
91
|
+
|
|
92
|
+
console.log('Created simulated stuck round (999998 stuck in "locking" for 10 minutes)');
|
|
93
|
+
|
|
94
|
+
// Check if detection works
|
|
95
|
+
const stuckRounds = await state.getStuckRounds();
|
|
96
|
+
const foundStuck = stuckRounds.find(r => r.round_id === stuckRoundId.toString());
|
|
97
|
+
|
|
98
|
+
if (foundStuck) {
|
|
99
|
+
console.log(`✅ Stuck Detection: Round 999998 detected as stuck`);
|
|
100
|
+
console.log(` Status: ${foundStuck.status}`);
|
|
101
|
+
console.log(` Stuck for: ${Math.floor(foundStuck.seconds_stuck)} seconds`);
|
|
102
|
+
} else {
|
|
103
|
+
console.log('❌ Stuck Detection: Failed to detect stuck round');
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Test isRoundStuck helper
|
|
108
|
+
const stuckRound = await state.getRound(stuckRoundId);
|
|
109
|
+
if (state.isRoundStuck(stuckRound)) {
|
|
110
|
+
console.log('✅ isRoundStuck: Helper function works correctly');
|
|
111
|
+
} else {
|
|
112
|
+
console.log('❌ isRoundStuck: Helper failed');
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
console.log('\n✅ TEST 2 PASSED: Stuck round detection works');
|
|
117
|
+
|
|
118
|
+
// ========================================================================
|
|
119
|
+
// TEST 3: Retry Tracking
|
|
120
|
+
// ========================================================================
|
|
121
|
+
console.log('\n\n🔄 TEST 3: Retry Tracking');
|
|
122
|
+
console.log('-'.repeat(80));
|
|
123
|
+
|
|
124
|
+
const retryRoundId = 999997;
|
|
125
|
+
await state.createRound(retryRoundId, 'revealing');
|
|
126
|
+
|
|
127
|
+
console.log('Simulating 3 failed attempts...');
|
|
128
|
+
for (let i = 1; i <= 3; i++) {
|
|
129
|
+
await state.incrementRetry(retryRoundId, `Test error #${i}`);
|
|
130
|
+
await state.logAction(retryRoundId, 'reveal', false, `Test error #${i}`, null, 500);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const retryRound = await state.getRound(retryRoundId);
|
|
134
|
+
|
|
135
|
+
if (retryRound.retry_count === 3 && retryRound.last_error === 'Test error #3') {
|
|
136
|
+
console.log(`✅ Retry Tracking: ${retryRound.retry_count} retries logged`);
|
|
137
|
+
console.log(`✅ Error Tracking: Last error stored: "${retryRound.last_error}"`);
|
|
138
|
+
} else {
|
|
139
|
+
console.log('❌ Retry Tracking: Failed');
|
|
140
|
+
console.log(` Expected 3 retries, got: ${retryRound.retry_count}`);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const retryActions = await state.getRoundActions(retryRoundId);
|
|
145
|
+
const failedActions = retryActions.filter(a => !a.success);
|
|
146
|
+
|
|
147
|
+
if (failedActions.length === 3) {
|
|
148
|
+
console.log(`✅ Action Log: All ${failedActions.length} failures logged`);
|
|
149
|
+
} else {
|
|
150
|
+
console.log('❌ Action Log: Failed');
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
console.log('\n✅ TEST 3 PASSED: Retry tracking works');
|
|
155
|
+
|
|
156
|
+
// ========================================================================
|
|
157
|
+
// TEST 4: Health Metrics
|
|
158
|
+
// ========================================================================
|
|
159
|
+
console.log('\n\n💊 TEST 4: Health Metrics');
|
|
160
|
+
console.log('-'.repeat(80));
|
|
161
|
+
|
|
162
|
+
console.log('Recording health snapshot...');
|
|
163
|
+
await state.recordHealth({
|
|
164
|
+
roundsCompleted: 42,
|
|
165
|
+
consecutiveFailures: 0,
|
|
166
|
+
lastSuccessRound: 551,
|
|
167
|
+
lastError: null,
|
|
168
|
+
uptimeSeconds: 3600
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const healthRecords = await state.pool.query(
|
|
172
|
+
'SELECT * FROM keeper_health ORDER BY timestamp DESC LIMIT 1'
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
if (healthRecords.rows.length > 0) {
|
|
176
|
+
const latest = healthRecords.rows[0];
|
|
177
|
+
console.log(`✅ Health Recording: Snapshot saved`);
|
|
178
|
+
console.log(` Rounds completed: ${latest.rounds_completed}`);
|
|
179
|
+
console.log(` Consecutive failures: ${latest.consecutive_failures}`);
|
|
180
|
+
console.log(` Uptime: ${latest.uptime_seconds}s`);
|
|
181
|
+
} else {
|
|
182
|
+
console.log('❌ Health Recording: Failed');
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
console.log('\n✅ TEST 4 PASSED: Health metrics work');
|
|
187
|
+
|
|
188
|
+
// ========================================================================
|
|
189
|
+
// TEST 5: Recovery Scenario Simulation
|
|
190
|
+
// ========================================================================
|
|
191
|
+
console.log('\n\n🔧 TEST 5: Recovery Scenario Simulation');
|
|
192
|
+
console.log('-'.repeat(80));
|
|
193
|
+
|
|
194
|
+
const recoveryRoundId = 999996;
|
|
195
|
+
|
|
196
|
+
console.log('Scenario: Keeper crashes after locking round but before reveal');
|
|
197
|
+
console.log(' 1. Create round in "locked" status (simulating successful lock)');
|
|
198
|
+
await state.createRound(recoveryRoundId, 'locked');
|
|
199
|
+
await state.updateRound(recoveryRoundId, {
|
|
200
|
+
locked_at: new Date(),
|
|
201
|
+
lock_signature: 'abc123...'
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
console.log(' 2. Check what recovery logic should do');
|
|
205
|
+
const roundToRecover = await state.getRound(recoveryRoundId);
|
|
206
|
+
|
|
207
|
+
if (roundToRecover.status === 'locked' && roundToRecover.locked_at && !roundToRecover.revealed_at) {
|
|
208
|
+
console.log('✅ Recovery Detection: Identified round needs reveal step');
|
|
209
|
+
console.log(' Next action: Should call revealRandomness()');
|
|
210
|
+
} else {
|
|
211
|
+
console.log('❌ Recovery Detection: Failed');
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
console.log('\n 3. Simulate successful recovery (complete reveal)');
|
|
216
|
+
await state.updateRound(recoveryRoundId, {
|
|
217
|
+
status: 'revealed',
|
|
218
|
+
revealed_at: new Date(),
|
|
219
|
+
reveal_signature: 'def456...'
|
|
220
|
+
});
|
|
221
|
+
await state.logAction(recoveryRoundId, 'reveal', true, null, 'def456...', 987);
|
|
222
|
+
|
|
223
|
+
const recoveredRound = await state.getRound(recoveryRoundId);
|
|
224
|
+
|
|
225
|
+
if (recoveredRound.status === 'revealed' && recoveredRound.revealed_at) {
|
|
226
|
+
console.log('✅ Recovery Execution: Round successfully recovered and progressed');
|
|
227
|
+
} else {
|
|
228
|
+
console.log('❌ Recovery Execution: Failed');
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
console.log('\n✅ TEST 5 PASSED: Recovery scenario works');
|
|
233
|
+
|
|
234
|
+
// ========================================================================
|
|
235
|
+
// TEST 6: Health Summary View
|
|
236
|
+
// ========================================================================
|
|
237
|
+
console.log('\n\n📊 TEST 6: Health Summary View');
|
|
238
|
+
console.log('-'.repeat(80));
|
|
239
|
+
|
|
240
|
+
const summary = await state.getHealthSummary();
|
|
241
|
+
|
|
242
|
+
if (summary) {
|
|
243
|
+
console.log('✅ Health Summary View works:');
|
|
244
|
+
console.log(` Resolved rounds: ${summary.resolved_rounds}`);
|
|
245
|
+
console.log(` Stuck rounds: ${summary.stuck_rounds}`);
|
|
246
|
+
console.log(` Rounds with retries: ${summary.rounds_with_retries}`);
|
|
247
|
+
console.log(` Avg completion time: ${summary.avg_completion_seconds || 'N/A'}s`);
|
|
248
|
+
} else {
|
|
249
|
+
console.log('❌ Health Summary: Failed');
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
console.log('\n✅ TEST 6 PASSED: Health summary view works');
|
|
254
|
+
|
|
255
|
+
// ========================================================================
|
|
256
|
+
// CLEANUP
|
|
257
|
+
// ========================================================================
|
|
258
|
+
console.log('\n\n🧹 Cleaning up test data...');
|
|
259
|
+
await state.pool.query('DELETE FROM keeper_rounds WHERE round_id >= 999996');
|
|
260
|
+
await state.pool.query('DELETE FROM keeper_actions WHERE round_id >= 999996');
|
|
261
|
+
console.log('✅ Test data cleaned up');
|
|
262
|
+
|
|
263
|
+
// ========================================================================
|
|
264
|
+
// FINAL VERDICT
|
|
265
|
+
// ========================================================================
|
|
266
|
+
console.log('\n\n' + '='.repeat(80));
|
|
267
|
+
console.log('🎉 ALL TESTS PASSED!');
|
|
268
|
+
console.log('='.repeat(80));
|
|
269
|
+
console.log(`
|
|
270
|
+
✅ Database CRUD operations work
|
|
271
|
+
✅ Stuck round detection works
|
|
272
|
+
✅ Retry tracking works
|
|
273
|
+
✅ Health metrics work
|
|
274
|
+
✅ Recovery scenario simulation works
|
|
275
|
+
✅ Health summary view works
|
|
276
|
+
|
|
277
|
+
The keeper state machine is PROVEN to work correctly.
|
|
278
|
+
Safe to rely on for production.
|
|
279
|
+
`);
|
|
280
|
+
console.log('='.repeat(80));
|
|
281
|
+
|
|
282
|
+
} catch (error) {
|
|
283
|
+
console.error('\n❌ TEST FAILED:', error.message);
|
|
284
|
+
console.error(error);
|
|
285
|
+
} finally {
|
|
286
|
+
await state.close();
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
runTests();
|
|
291
|
+
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple webhook test receiver
|
|
3
|
+
* Run: node scripts/test-webhook-receiver.js
|
|
4
|
+
* Listens on port 3002 and logs all incoming webhook payloads
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const http = require('http');
|
|
8
|
+
const crypto = require('crypto');
|
|
9
|
+
|
|
10
|
+
const PORT = 3005;
|
|
11
|
+
|
|
12
|
+
// If you have a webhook secret, paste it here to verify signatures
|
|
13
|
+
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET || '';
|
|
14
|
+
|
|
15
|
+
const server = http.createServer((req, res) => {
|
|
16
|
+
if (req.method !== 'POST') {
|
|
17
|
+
res.writeHead(200);
|
|
18
|
+
res.end('Webhook receiver running. POST to / to test.');
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let body = '';
|
|
23
|
+
req.on('data', chunk => { body += chunk; });
|
|
24
|
+
req.on('end', () => {
|
|
25
|
+
const event = req.headers['x-dubs-event'] || '(none)';
|
|
26
|
+
const signature = req.headers['x-dubs-signature'] || '(none)';
|
|
27
|
+
|
|
28
|
+
console.log('\n' + '='.repeat(60));
|
|
29
|
+
console.log(`📥 WEBHOOK RECEIVED — ${new Date().toLocaleTimeString()}`);
|
|
30
|
+
console.log('='.repeat(60));
|
|
31
|
+
console.log(`Event: ${event}`);
|
|
32
|
+
console.log(`Signature: ${signature}`);
|
|
33
|
+
|
|
34
|
+
// Verify signature if secret is set
|
|
35
|
+
if (WEBHOOK_SECRET) {
|
|
36
|
+
const expected = crypto.createHmac('sha256', WEBHOOK_SECRET).update(body).digest('hex');
|
|
37
|
+
const valid = expected === signature;
|
|
38
|
+
console.log(`Verified: ${valid ? '✅ VALID' : '❌ INVALID'}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const parsed = JSON.parse(body);
|
|
43
|
+
console.log(`\nPayload:`);
|
|
44
|
+
console.log(JSON.stringify(parsed, null, 2));
|
|
45
|
+
} catch {
|
|
46
|
+
console.log(`\nRaw body: ${body}`);
|
|
47
|
+
}
|
|
48
|
+
console.log('='.repeat(60));
|
|
49
|
+
|
|
50
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
51
|
+
res.end(JSON.stringify({ received: true }));
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
server.listen(PORT, () => {
|
|
56
|
+
console.log(`\n🎯 Webhook test receiver listening on http://localhost:${PORT}`);
|
|
57
|
+
console.log(`\nRegister this URL in the Webhooks tab:`);
|
|
58
|
+
console.log(` http://localhost:${PORT}`);
|
|
59
|
+
console.log(`\nThen create or join a game in the playground — you'll see the payload here.\n`);
|
|
60
|
+
});
|