EvalueTonSavoir/server/__tests__/oidc.test.js

234 lines
8.8 KiB
JavaScript
Raw Normal View History

2025-03-06 18:22:40 -05:00
let PassportOpenIDConnect = require('../auth/modules/passport-providers/oidc');
const AppError = require('../middleware/AppError');
const authUserAssoc = require('../models/authUserAssociation');
const userModel = require('../models/users');
global.fetch = jest.fn();
// Mock the authUserAssoc methods
jest.mock('../models/authUserAssociation', () => ({
find_user_association: jest.fn(),
link: jest.fn(),
unlink: jest.fn(),
}));
// Mock userModel methods
jest.mock('../models/users', () => ({
getById: jest.fn(),
getId: jest.fn(),
generatePassword: jest.fn(),
register: jest.fn(),
editUser: jest.fn(),
}));
// Mock db connection methods
jest.mock('../config/db', () => ({
connect: jest.fn(),
getConnection: jest.fn(() => ({
collection: jest.fn(() => ({
findOne: jest.fn(),
insertOne: jest.fn(),
deleteOne: jest.fn(),
})),
})),
}));
const getProfileMock = (id = 'test-auth-id', email = 'test@example.com', name = 'Test User', roles = ['teacher']) => ({
id,
emails: [{ value: email }],
name: { displayName: name },
roles,
});
const setupMocks = (authAssocReturn = null, userReturn = null, fetchReturn = {}) => {
// Mock authUserAssoc and userModel
authUserAssoc.find_user_association.mockResolvedValue(authAssocReturn);
userModel.getId.mockResolvedValue(userReturn);
userModel.getById.mockResolvedValue(userReturn);
// Mock fetch to return a fake configuration
fetch.mockResolvedValue({
json: jest.fn().mockResolvedValue(fetchReturn),
});
};
const createOidcConfig = () => ({
OIDC_CONFIG_URL: 'https://example.com/.well-known/openid-configuration',
OIDC_CLIENT_ID: 'test-client-id',
OIDC_CLIENT_SECRET: 'test-client-secret',
OIDC_ADD_SCOPE: '',
OIDC_ROLE_TEACHER_VALUE: 'teacher',
OIDC_ROLE_STUDENT_VALUE: 'student',
tokenURL: 'https://example.com/token',
});
describe('PassportOpenIDConnect Class', () => {
let passportMock;
let appMock;
let oidcInstance;
beforeEach(() => {
passportMock = { use: jest.fn(), register: jest.fn(), authenticate: jest.fn()};
appMock = { get: jest.fn() };
// Mock configuration object
const oidcConfig = {
issuer: 'exom', // Ensure the issuer is correctly set
authorization_endpoint: 'https://example.com/auth',
token_endpoint: 'https://example.com/token',
tokenURL: 'https://example.com/token',
userInfoURL: 'https://example.com/userinfo',
clientID: 'test-client-id',
clientSecret: 'test-client-secret',
callbackURL: 'https://example.com/callback',
passReqToCallback: true,
scope: 'openid profile email', // Set required scopes
};
// Mock passport's use method to simulate adding the strategy
passportMock.use.mockImplementation((strategy, callback) => {
passportMock._callback = callback;
});
passportMock.register = jest.fn().mockResolvedValue(getProfileMock());
// Instantiate the PassportOpenIDConnect class with the mock
oidcInstance = new PassportOpenIDConnect(passportMock, oidcConfig);
});
afterEach(() => {
jest.clearAllMocks();
});
it('should fetch OIDC configuration', async () => {
const fakeConfig = { issuer: 'exom', authorization_endpoint: 'https://example.com/auth' };
fetch.mockResolvedValue({ json: jest.fn().mockResolvedValue(fakeConfig) });
const result = await oidcInstance.getConfigFromConfigURL('test-provider', { OIDC_CONFIG_URL: 'https://example.com/.well-known/openid-configuration' });
expect(fetch).toHaveBeenCalledWith('https://example.com/.well-known/openid-configuration');
expect(result).toEqual(fakeConfig);
});
it('should throw AppError if fetching OIDC config fails', async () => {
fetch.mockRejectedValue(new Error('Network error'));
await expect(oidcInstance.getConfigFromConfigURL('test-provider', { OIDC_CONFIG_URL: 'https://example.com' }))
.rejects.toThrow(AppError);
});
it('should register passport strategy and process new users consent flag true', async () => {
setupMocks(null, null, {
issuer: 'https://example.com',
authorization_endpoint: 'https://example.com/auth',
token_endpoint: 'https://example.com/token'
});
const profileMock = getProfileMock();
const reqMock = { session: {} };
const doneMock = jest.fn((error, user) => {
console.log('doneMock was called:', error, user); // Log to check if doneMock is called
});
// Updated configuration with tokenURL
const config = createOidcConfig();
// Ensure `passportMock.use` is being called correctly with the expected config
await oidcInstance.register(appMock, passportMock, '/auth', 'oidc-test', config, userModel);
// Check that passport.use has been called, which registers the strategy
expect(passportMock.use).toHaveBeenCalledWith('oidc-test', expect.objectContaining({
_issuer: 'https://example.com', // Assert that tokenURL is present in the strategy config
}));
// Ensure routes are defined
expect(appMock.get).toHaveBeenCalledWith('/auth/oidc-test', expect.any(Function));
expect(appMock.get).toHaveBeenCalledWith('/auth/oidc-test/callback', expect.any(Function), expect.any(Function));
// Access the _verify method inside the passportMock._callback (which is a Strategy instance)
const strategyCallback = passportMock._callback._verify;
// Ensure the callback is correctly registered and is a function
expect(typeof strategyCallback).toBe('function');
doneMock.mockImplementation((error, user) => {
// Check if session.requiresConsent is set
console.log("In doneMock, error:", error, "user:", user);
});
// Simulate the strategy callback being invoked
await strategyCallback(reqMock, null, profileMock, null, null, doneMock, null, userModel);
// Ensure the doneMock was called
expect(doneMock).toHaveBeenCalled();
// Validate that consent flag is set for new users
expect(reqMock.session.requiresConsent).toBe(true);
});
it('should correctly process new users and set consent flag', async () => {
setupMocks(null, { _id: 'mocked-user-id', email: 'test@example.com' }, {
issuer: 'https://example.com',
authorization_endpoint: 'https://example.com/auth',
token_endpoint: 'https://example.com/token'
});
const config = createOidcConfig();
const profileMock = getProfileMock();
const reqMock = { session: {} };
const doneMock = jest.fn((error, user) => {
console.log('doneMock was called:', error, user);
});
passportMock.register.mockResolvedValue({ _id: 'new-user-id' });
await oidcInstance.register(appMock, passportMock, '/auth', 'oidc-test', config, userModel);
const strategyCallback = passportMock._callback._verify;
expect(typeof strategyCallback).toBe('function');
doneMock.mockImplementation((error, user) => {
console.log("In doneMock, error:", error, "user:", user);
});
await strategyCallback(reqMock, null, profileMock, null, null, doneMock, null, userModel);
expect(doneMock).toHaveBeenCalled();
expect(reqMock.session.requiresConsent).toBe(true);
});
it('should correctly process existing users without setting consent flag', async () => {
let linkData = {
user_id: 'existing-user-id',
provider_id: 'test-auth-id',
provider: 'oidc',
email: 'test@example.com',
role: 'teacher',
};
setupMocks(linkData, { _id: 'mocked-user-id', email: 'test@example.com' }, {
issuer: 'https://example.com',
authorization_endpoint: 'https://example.com/auth',
token_endpoint: 'https://example.com/token'
});
const config = createOidcConfig();
const profileMock = getProfileMock();
const reqMock = { session: {} };
const doneMock = jest.fn((error, user) => {
console.log('doneMock was called:', error, user);
});
await oidcInstance.register(appMock, passportMock, '/auth', 'oidc-test', config, userModel);
const strategyCallback = passportMock._callback._verify;
expect(typeof strategyCallback).toBe('function');
await strategyCallback(reqMock, null, profileMock, null, null, doneMock, null, userModel);
expect(doneMock).toHaveBeenCalled();
expect(reqMock.session.requiresConsent).toBe(false);
});
});