import { useraccesstokens } from '../src/client/useraccesstokens.js' import { suite, test } from 'node:test' import assert from 'node:assert' import fs from 'node:fs/promises' import path from 'node:path' import { fileURLToPath } from 'node:url' import { TokenManager } from '../server/server.js' const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) suite('useraccesstokens plugin', () => { suite('client expand', () => { test('can escape html markup characters', () => { const result = useraccesstokens.expand('try < & >') assert.equal(result, 'try < & >') }) test('can format italic text', () => { const result = useraccesstokens.expand('This is *italic* text') assert.equal(result, 'This is italic text') }) }) suite('server TokenManager', async () => { let tempDir let tokenManager // Realistic user objects that match what OAuth2/Passport provide const testUser1 = { displayName: 'John Doe', email: 'john@example.com', provider: 'github', id: '12345' } const testUser2 = { displayName: 'Jane Smith', email: 'jane@example.com', provider: 'google', id: '67890' } // Same user object but different reference (tests object equality) const testUser1Copy = { displayName: 'John Doe', email: 'john@example.com', provider: 'github', id: '12345' } // Setup before each test const setup = async () => { tempDir = path.join(__dirname, 'temp-' + Date.now()) await fs.mkdir(tempDir, { recursive: true }) tokenManager = new (class extends TokenManager { constructor(statusPath) { super(statusPath) } })(tempDir) } // Cleanup after each test const cleanup = async () => { if (tempDir) { await fs.rm(tempDir, { recursive: true, force: true }) } } test('can generate tokens with correct prefix', async () => { await setup() try { const token = tokenManager.generateToken() assert(token.startsWith('fwuat-')) assert(token.length > 20) // Should be reasonably long } finally { await cleanup() } }) test('can create and validate tokens', async () => { await setup() try { const result = await tokenManager.createToken(testUser1, 'test-token') assert(result.token.startsWith('fwuat-')) assert.equal(result.record.name, 'test-token') assert.deepEqual(result.record.user, testUser1) assert.equal(result.record.revoked, false) assert(result.record.created) assert(result.record.displayHint) const validation = await tokenManager.validateToken(result.token) assert(validation) assert.deepEqual(validation.user, testUser1) assert.equal(validation.name, 'test-token') } finally { await cleanup() } }) test('can list user tokens safely', async () => { await setup() try { await tokenManager.createToken(testUser1, 'token1') await tokenManager.createToken(testUser1, 'token2') await tokenManager.createToken(testUser2, 'token3') const user1Tokens = await tokenManager.listTokens(testUser1) assert.equal(user1Tokens.length, 2) // Verify tokenHash is not included in the response user1Tokens.forEach(token => { assert(!token.tokenHash) assert(token.displayHint) assert.deepEqual(token.user, testUser1) }) const user2Tokens = await tokenManager.listTokens(testUser2) assert.equal(user2Tokens.length, 1) } finally { await cleanup() } }) test('can revoke tokens', async () => { await setup() try { const result = await tokenManager.createToken(testUser1, 'test-token') // Token should work before revocation let validation = await tokenManager.validateToken(result.token) assert(validation) // Revoke the token await tokenManager.revokeToken(testUser1, 'test-token') // Token should not work after revocation validation = await tokenManager.validateToken(result.token) assert.equal(validation, null) } finally { await cleanup() } }) test('rejects duplicate token names for same user', async () => { await setup() try { await tokenManager.createToken(testUser1, 'duplicate-name') try { await tokenManager.createToken(testUser1, 'duplicate-name') assert.fail('Should have thrown an error for duplicate name') } catch (error) { assert(error.message.includes('already exists')) } } finally { await cleanup() } }) test('handles token expiration', async () => { await setup() try { // Create token that expires immediately (for testing) const result = await tokenManager.createToken(testUser1, 'expired-token', -1) // Token should be expired and not validate const validation = await tokenManager.validateToken(result.token) assert.equal(validation, null) } finally { await cleanup() } }) test('correctly handles object equality for user comparisons', async () => { await setup() try { // Create token with testUser1 const result = await tokenManager.createToken(testUser1, 'test-token') // List tokens using testUser1Copy (same content, different object reference) const tokens = await tokenManager.listTokens(testUser1Copy) assert.equal(tokens.length, 1) assert.equal(tokens[0].name, 'test-token') // Revoke token using testUser1Copy await tokenManager.revokeToken(testUser1Copy, 'test-token') // Verify token is revoked const validation = await tokenManager.validateToken(result.token) assert.equal(validation, null) } finally { await cleanup() } }) test('can delete tokens with object user equality', async () => { await setup() try { await tokenManager.createToken(testUser1, 'token-to-delete') // Verify token exists let tokens = await tokenManager.listTokens(testUser1) assert.equal(tokens.length, 1) // Delete using testUser1Copy (same content, different reference) await tokenManager.deleteToken(testUser1Copy, 'token-to-delete') // Verify token is gone tokens = await tokenManager.listTokens(testUser1) assert.equal(tokens.length, 0) } finally { await cleanup() } }) }) })