File size: 5,972 Bytes
e0e1da7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
// 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);
});