process.env.JWT_SECRET = process.env.JWT_SECRET || 'test-secret' process.env.DB_HOST = '127.0.0.1' process.env.DB_PORT = '59999' const { test, after, beforeEach, afterEach } = require('node:test') const assert = require('node:assert/strict') const GoogleProvider = require('../src/auth/providers/google.provider') const DiscordProvider = require('../src/auth/providers/discord.provider') const GenericOidcProvider = require('../src/auth/providers/genericOidc.provider') const db = require('../src/utils/db') after(() => db.close()) const realFetch = global.fetch afterEach(() => { global.fetch = realFetch }) // Install a fetch stub that answers by URL substring. function mockFetch(routes) { global.fetch = async (url) => { for (const [needle, payload] of Object.entries(routes)) { if (String(url).includes(needle)) { return { ok: true, status: 200, json: async () => payload, text: async () => JSON.stringify(payload) } } } return { ok: false, status: 404, text: async () => 'not found' } } } test('Google getAuthorizationUrl includes client_id, redirect_uri, scope, state, PKCE', () => { const p = new GoogleProvider({ id: 'google', clientId: 'gid', clientSecret: 'gsecret' }) const url = p.getAuthorizationUrl('the-state', { redirectUri: 'https://app/cb', codeChallenge: 'CHAL' }) assert.ok(url.startsWith('https://accounts.google.com/o/oauth2/v2/auth?')) const q = new URL(url).searchParams assert.equal(q.get('client_id'), 'gid') assert.equal(q.get('redirect_uri'), 'https://app/cb') assert.equal(q.get('response_type'), 'code') assert.equal(q.get('scope'), 'openid email profile') assert.equal(q.get('state'), 'the-state') assert.equal(q.get('code_challenge'), 'CHAL') assert.equal(q.get('code_challenge_method'), 'S256') }) test('Google handleCallback exchanges code and normalizes the profile', async () => { mockFetch({ 'oauth2.googleapis.com/token': { access_token: 'AT' }, 'openidconnect.googleapis.com/v1/userinfo': { sub: '11550', email: 'alice@example.com', name: 'Alice' }, }) const p = new GoogleProvider({ id: 'google', clientId: 'gid', clientSecret: 'gsecret' }) const profile = await p.handleCallback({ code: 'C', redirectUri: 'https://app/cb', codeVerifier: 'V' }) assert.deepEqual(profile, { subject: '11550', email: 'alice@example.com', name: 'Alice' }) }) test('Discord authorize URL + profile mapping (global_name → name, id → subject)', async () => { const p = new DiscordProvider({ id: 'discord', clientId: 'did', clientSecret: 'dsecret' }) const url = p.getAuthorizationUrl('s', { redirectUri: 'https://app/cb' }) assert.ok(url.startsWith('https://discord.com/oauth2/authorize?')) assert.equal(new URL(url).searchParams.get('scope'), 'identify email') mockFetch({ 'discord.com/api/oauth2/token': { access_token: 'AT' }, 'discord.com/api/users/@me': { id: '99', username: 'bob', global_name: 'Bob', email: 'bob@x.io' }, }) const profile = await p.handleCallback({ code: 'C', redirectUri: 'https://app/cb' }) assert.deepEqual(profile, { subject: '99', email: 'bob@x.io', name: 'Bob' }) }) test('Generic OIDC provider uses configured endpoints and OIDC profile fields', async () => { const p = new GenericOidcProvider({ id: 'authentik', kind: 'oidc', clientId: 'cid', clientSecret: 'csec', authorizeUrl: 'https://idp.example/authorize', tokenUrl: 'https://idp.example/token', userinfoUrl: 'https://idp.example/userinfo', scopes: 'openid email', }) const url = p.getAuthorizationUrl('s', { redirectUri: 'https://app/cb', codeChallenge: 'CH' }) assert.ok(url.startsWith('https://idp.example/authorize?')) assert.equal(new URL(url).searchParams.get('scope'), 'openid email') mockFetch({ 'idp.example/token': { access_token: 'AT' }, 'idp.example/userinfo': { sub: 'abc', email: 'c@d.e', preferred_username: 'carol' }, }) const profile = await p.handleCallback({ code: 'C', redirectUri: 'https://app/cb', codeVerifier: 'V' }) assert.deepEqual(profile, { subject: 'abc', email: 'c@d.e', name: 'carol' }) }) test('handleCallback throws when the token exchange fails', async () => { mockFetch({}) // everything 404s const p = new GoogleProvider({ id: 'google', clientId: 'gid', clientSecret: 'gsecret' }) await assert.rejects(() => p.handleCallback({ code: 'C', redirectUri: 'https://app/cb', codeVerifier: 'V' })) })