// Setup testing database environment variables BEFORE any imports process.env.DB_MODE = 'sqlite'; process.env.SQLITE_DB_PATH = './test_database.db'; // Clear Supabase keys to force SQLite mode process.env.SUPABASE_URL = ''; process.env.SUPABASE_KEY = ''; process.env.SUPABASE_SERVICE_ROLE_KEY = ''; // Mock environment variables for Twitch client process.env.TWITCH_CLIENT_ID = 'mocked_client_id'; process.env.TWITCH_CLIENT_SECRET = 'mocked_client_secret'; import Database from 'better-sqlite3'; import fs from 'fs'; import axios from 'axios'; // Clean up test database if (fs.existsSync('./test_database.db')) { fs.unlinkSync('./test_database.db'); } const sqliteDb = new Database('./test_database.db'); // Create settings table sqliteDb.exec(` CREATE TABLE IF NOT EXISTS settings ( key TEXT PRIMARY KEY, value TEXT NOT NULL ); `); // Insert initial expired token and refresh token sqliteDb.prepare(` INSERT INTO settings (key, value) VALUES ('twitch_streamer_access_token', 'expired_token_value') `).run(); sqliteDb.prepare(` INSERT INTO settings (key, value) VALUES ('twitch_streamer_refresh_token', 'valid_refresh_token_value') `).run(); // Mock Axios methods let validateCalls = 0; let refreshCalls = 0; let refreshParamsReceived = null; axios.get = async (url, config) => { if (url === 'https://id.twitch.tv/oauth2/validate') { validateCalls++; const authHeader = config?.headers?.Authorization; if (authHeader === 'Bearer valid_mocked_token') { // Mock success for valid token return { data: { client_id: 'mocked_client_id' } }; } else { // Mock expiration/invalid token error const err = new Error('Unauthorized'); err.response = { status: 401, data: { message: 'invalid oauth token' } }; throw err; } } throw new Error(`Unexpected GET request to ${url}`); }; axios.post = async (url, data, config) => { if (url === 'https://id.twitch.tv/oauth2/token') { refreshCalls++; refreshParamsReceived = config?.params; return { data: { access_token: 'new_refreshed_access_token', refresh_token: 'new_refreshed_refresh_token' } }; } throw new Error(`Unexpected POST request to ${url}`); }; // Dynamically import db.js and eventsub.js so they read correct environment variables and use the mocked axios const { getSetting } = await import('../db.js'); const { getOrRefreshAccessToken } = await import('../eventsub.js'); async function runTest() { console.log('--- STARTING TOKEN REFRESH TEST ---'); // Scenario 1: Initial call with expired token. Should fail validate, call refresh, and return new token. console.log('Scenario 1: Testing token validation failure and refresh flow...'); const token = await getOrRefreshAccessToken(); console.log('Returned token:', token); console.log('Validate calls:', validateCalls); console.log('Refresh calls:', refreshCalls); console.log('Params received on refresh:', refreshParamsReceived); // Assertions if (token !== 'new_refreshed_access_token') { console.error('FAIL: Expected new refreshed access token, got:', token); process.exit(1); } if (validateCalls !== 1) { console.error('FAIL: Expected 1 validate call, got:', validateCalls); process.exit(1); } if (refreshCalls !== 1) { console.error('FAIL: Expected 1 refresh call, got:', refreshCalls); process.exit(1); } if (!refreshParamsReceived || refreshParamsReceived.refresh_token !== 'valid_refresh_token_value') { console.error('FAIL: Incorrect refresh token sent in params:', refreshParamsReceived); process.exit(1); } // Verify DB updated settings const dbAccessToken = sqliteDb.prepare("SELECT value FROM settings WHERE key = 'twitch_streamer_access_token'").get()?.value; const dbRefreshToken = sqliteDb.prepare("SELECT value FROM settings WHERE key = 'twitch_streamer_refresh_token'").get()?.value; console.log('DB Access Token:', dbAccessToken); console.log('DB Refresh Token:', dbRefreshToken); if (dbAccessToken !== 'new_refreshed_access_token' || dbRefreshToken !== 'new_refreshed_refresh_token') { console.error('FAIL: Settings not updated in database correctly'); process.exit(1); } // Scenario 2: Subsequent call with the now valid token. Should validate successfully and not call refresh. console.log('Scenario 2: Testing valid token bypasses refresh...'); // Update mock token validation check to succeed for this new token axios.get = async (url, config) => { if (url === 'https://id.twitch.tv/oauth2/validate') { validateCalls++; const authHeader = config?.headers?.Authorization; if (authHeader === 'Bearer new_refreshed_access_token') { return { data: { client_id: 'mocked_client_id' } }; } const err = new Error('Unauthorized'); err.response = { status: 401 }; throw err; } throw new Error(`Unexpected GET request to ${url}`); }; const tokenScenario2 = await getOrRefreshAccessToken(); console.log('Scenario 2 Returned token:', tokenScenario2); console.log('Scenario 2 Validate calls total:', validateCalls); console.log('Scenario 2 Refresh calls total:', refreshCalls); if (tokenScenario2 !== 'new_refreshed_access_token') { console.error('FAIL: Expected token to remain new_refreshed_access_token, got:', tokenScenario2); process.exit(1); } if (validateCalls !== 2) { console.error('FAIL: Expected validate calls to increment to 2, got:', validateCalls); process.exit(1); } if (refreshCalls !== 1) { console.error('FAIL: Expected refresh calls to remain 1, got:', refreshCalls); process.exit(1); } console.log('SUCCESS: Token refresh flow successfully verified and tested!'); // Clean up sqliteDb.close(); if (fs.existsSync('./test_database.db')) { fs.unlinkSync('./test_database.db'); } } runTest().catch(err => { console.error('Test crashed:', err); process.exit(1); });