219 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			219 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 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 <i>italic</i> 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()
 | |
|       }
 | |
|     })
 | |
|   })
 | |
| })
 |