151 lines
3.6 KiB
TypeScript
151 lines
3.6 KiB
TypeScript
/**
|
|
* Tests for AuthManager singleton behavior
|
|
*/
|
|
|
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
|
|
// Mock the logger to verify warnings (must be hoisted before SUT import)
|
|
const mockLogger = {
|
|
warn: vi.fn(),
|
|
info: vi.fn(),
|
|
debug: vi.fn(),
|
|
error: vi.fn()
|
|
};
|
|
|
|
vi.mock('../logger/index.js', () => ({
|
|
getLogger: () => mockLogger
|
|
}));
|
|
|
|
// Spy on CredentialStore constructor to verify config propagation
|
|
const CredentialStoreSpy = vi.fn();
|
|
vi.mock('./credential-store.js', () => {
|
|
return {
|
|
CredentialStore: class {
|
|
constructor(config: any) {
|
|
CredentialStoreSpy(config);
|
|
this.getCredentials = vi.fn(() => null);
|
|
}
|
|
getCredentials() {
|
|
return null;
|
|
}
|
|
saveCredentials() {}
|
|
clearCredentials() {}
|
|
hasValidCredentials() {
|
|
return false;
|
|
}
|
|
}
|
|
};
|
|
});
|
|
|
|
// Mock OAuthService to avoid side effects
|
|
vi.mock('./oauth-service.js', () => {
|
|
return {
|
|
OAuthService: class {
|
|
constructor() {}
|
|
authenticate() {
|
|
return Promise.resolve({});
|
|
}
|
|
getAuthorizationUrl() {
|
|
return null;
|
|
}
|
|
}
|
|
};
|
|
});
|
|
|
|
// Mock SupabaseAuthClient to avoid side effects
|
|
vi.mock('../clients/supabase-client.js', () => {
|
|
return {
|
|
SupabaseAuthClient: class {
|
|
constructor() {}
|
|
refreshSession() {
|
|
return Promise.resolve({});
|
|
}
|
|
signOut() {
|
|
return Promise.resolve();
|
|
}
|
|
}
|
|
};
|
|
});
|
|
|
|
// Import SUT after mocks
|
|
import { AuthManager } from './auth-manager.js';
|
|
|
|
describe('AuthManager Singleton', () => {
|
|
beforeEach(() => {
|
|
// Reset singleton before each test
|
|
AuthManager.resetInstance();
|
|
vi.clearAllMocks();
|
|
CredentialStoreSpy.mockClear();
|
|
});
|
|
|
|
it('should return the same instance on multiple calls', () => {
|
|
const instance1 = AuthManager.getInstance();
|
|
const instance2 = AuthManager.getInstance();
|
|
|
|
expect(instance1).toBe(instance2);
|
|
});
|
|
|
|
it('should use config on first call', () => {
|
|
const config = {
|
|
baseUrl: 'https://test.auth.com',
|
|
configDir: '/test/config',
|
|
configFile: '/test/config/auth.json'
|
|
};
|
|
|
|
const instance = AuthManager.getInstance(config);
|
|
expect(instance).toBeDefined();
|
|
|
|
// Assert that CredentialStore was constructed with the provided config
|
|
expect(CredentialStoreSpy).toHaveBeenCalledTimes(1);
|
|
expect(CredentialStoreSpy).toHaveBeenCalledWith(config);
|
|
|
|
// Verify the config is passed to internal components through observable behavior
|
|
// getCredentials would look in the configured file path
|
|
const credentials = instance.getCredentials();
|
|
expect(credentials).toBeNull(); // File doesn't exist, but config was propagated correctly
|
|
});
|
|
|
|
it('should warn when config is provided after initialization', () => {
|
|
// Clear previous calls
|
|
mockLogger.warn.mockClear();
|
|
|
|
// First call with config
|
|
AuthManager.getInstance({ baseUrl: 'https://first.auth.com' });
|
|
|
|
// Second call with different config
|
|
AuthManager.getInstance({ baseUrl: 'https://second.auth.com' });
|
|
|
|
// Verify warning was logged
|
|
expect(mockLogger.warn).toHaveBeenCalledWith(
|
|
expect.stringMatching(/config.*after initialization.*ignored/i)
|
|
);
|
|
});
|
|
|
|
it('should not warn when no config is provided after initialization', () => {
|
|
// Clear previous calls
|
|
mockLogger.warn.mockClear();
|
|
|
|
// First call with config
|
|
AuthManager.getInstance({ configDir: '/test/config' });
|
|
|
|
// Second call without config
|
|
AuthManager.getInstance();
|
|
|
|
// Verify no warning was logged
|
|
expect(mockLogger.warn).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should allow resetting the instance', () => {
|
|
const instance1 = AuthManager.getInstance();
|
|
|
|
// Reset the instance
|
|
AuthManager.resetInstance();
|
|
|
|
// Get new instance
|
|
const instance2 = AuthManager.getInstance();
|
|
|
|
// They should be different instances
|
|
expect(instance1).not.toBe(instance2);
|
|
});
|
|
});
|