UNPKG

matterbridge-roborock-vacuum-plugin

Version:
209 lines 14.2 kB
import { beforeEach, describe, expect, it, vi } from 'vitest'; import { PasswordAuthStrategy } from '../../../services/authentication/PasswordAuthStrategy.js'; import { asPartial, asType, createMockLogger } from '../../testUtils.js'; describe('PasswordAuthStrategy', () => { let strategy; let mockAuthService; let mockUserDataRepository; let mockConfigManager; let mockLogger; const mockUserData = asPartial({ uid: 'user123', username: 'test@example.com', token: 'valid-token', tokentype: 'Bearer', region: 'us', }); beforeEach(() => { mockLogger = createMockLogger(); mockAuthService = asPartial({ loginWithPassword: vi.fn(), loginWithCachedToken: vi.fn(), loginWithVerificationCode: vi.fn(), }); mockUserDataRepository = asPartial({ loadUserData: vi.fn(), saveUserData: vi.fn(), clearUserData: vi.fn(), }); const mockRawConfig = asPartial({ authentication: asPartial({ forceAuthentication: false, }), }); mockConfigManager = asPartial({ get rawConfig() { return mockRawConfig; }, get alwaysExecuteAuthentication() { return mockRawConfig.authentication.forceAuthentication ?? false; }, }); strategy = new PasswordAuthStrategy(mockAuthService, mockUserDataRepository, mockConfigManager, mockLogger); vi.clearAllMocks(); }); describe('authenticate', () => { const createContext = (username, password) => ({ username, password, }); describe('cached token authentication', () => { it('should authenticate successfully with cached token', async () => { const context = createContext('test@example.com', 'password123'); vi.mocked(mockUserDataRepository.loadUserData).mockResolvedValue(mockUserData); vi.mocked(mockAuthService.loginWithCachedToken).mockResolvedValue(mockUserData); const result = await strategy.authenticate(context); expect(result).toEqual(mockUserData); expect(mockUserDataRepository.loadUserData).toHaveBeenCalledWith('test@example.com'); expect(mockAuthService.loginWithCachedToken).toHaveBeenCalledWith('test@example.com', mockUserData); expect(mockAuthService.loginWithPassword).not.toHaveBeenCalled(); expect(mockLogger.notice).toHaveBeenCalledWith('Successfully authenticated with cached token'); }); it('should skip cached token when alwaysExecuteAuthentication is true', async () => { asType(mockConfigManager.rawConfig.authentication).forceAuthentication = true; const context = createContext('test@example.com', 'password123'); vi.mocked(mockAuthService.loginWithPassword).mockResolvedValue(mockUserData); vi.mocked(mockUserDataRepository.saveUserData).mockResolvedValue(); const result = await strategy.authenticate(context); expect(mockUserDataRepository.clearUserData).toHaveBeenCalled(); expect(mockUserDataRepository.loadUserData).not.toHaveBeenCalled(); expect(mockAuthService.loginWithCachedToken).not.toHaveBeenCalled(); expect(mockAuthService.loginWithPassword).toHaveBeenCalledWith('test@example.com', 'password123'); expect(result).toEqual(mockUserData); }); it('should fall back to password auth when no cached token exists', async () => { const context = createContext('test@example.com', 'password123'); vi.mocked(mockUserDataRepository.loadUserData).mockResolvedValue(undefined); vi.mocked(mockAuthService.loginWithPassword).mockResolvedValue(mockUserData); vi.mocked(mockUserDataRepository.saveUserData).mockResolvedValue(); const result = await strategy.authenticate(context); expect(mockUserDataRepository.loadUserData).toHaveBeenCalledWith('test@example.com'); expect(mockAuthService.loginWithCachedToken).not.toHaveBeenCalled(); expect(mockAuthService.loginWithPassword).toHaveBeenCalledWith('test@example.com', 'password123'); expect(result).toEqual(mockUserData); }); it('should fall back to password auth when cached token is invalid', async () => { const context = createContext('test@example.com', 'password123'); const tokenError = new Error('Token expired'); vi.mocked(mockUserDataRepository.loadUserData).mockResolvedValue(mockUserData); vi.mocked(mockAuthService.loginWithCachedToken).mockRejectedValue(tokenError); vi.mocked(mockAuthService.loginWithPassword).mockResolvedValue(mockUserData); vi.mocked(mockUserDataRepository.saveUserData).mockResolvedValue(); vi.mocked(mockUserDataRepository.clearUserData).mockResolvedValue(); const result = await strategy.authenticate(context); expect(mockAuthService.loginWithCachedToken).toHaveBeenCalledWith('test@example.com', mockUserData); expect(mockUserDataRepository.clearUserData).toHaveBeenCalled(); expect(mockLogger.warn).toHaveBeenCalledWith('Cached token invalid or expired: Token expired'); expect(mockAuthService.loginWithPassword).toHaveBeenCalledWith('test@example.com', 'password123'); expect(result).toEqual(mockUserData); }); it('should handle non-Error objects when cached token fails', async () => { const context = createContext('test@example.com', 'password123'); vi.mocked(mockUserDataRepository.loadUserData).mockResolvedValue(mockUserData); vi.mocked(mockAuthService.loginWithCachedToken).mockRejectedValue('String error'); vi.mocked(mockAuthService.loginWithPassword).mockResolvedValue(mockUserData); vi.mocked(mockUserDataRepository.saveUserData).mockResolvedValue(); vi.mocked(mockUserDataRepository.clearUserData).mockResolvedValue(); const result = await strategy.authenticate(context); expect(mockLogger.warn).toHaveBeenCalledWith('Cached token invalid or expired: String error'); expect(mockAuthService.loginWithPassword).toHaveBeenCalledWith('test@example.com', 'password123'); expect(result).toEqual(mockUserData); }); }); describe('password authentication', () => { it('should authenticate successfully with password', async () => { const context = createContext('test@example.com', 'password123'); vi.mocked(mockUserDataRepository.loadUserData).mockResolvedValue(undefined); vi.mocked(mockAuthService.loginWithPassword).mockResolvedValue(mockUserData); vi.mocked(mockUserDataRepository.saveUserData).mockResolvedValue(); const result = await strategy.authenticate(context); expect(mockAuthService.loginWithPassword).toHaveBeenCalledWith('test@example.com', 'password123'); expect(mockUserDataRepository.saveUserData).toHaveBeenCalledWith(mockUserData); expect(mockLogger.notice).toHaveBeenCalledWith('Authentication successful!'); expect(result).toEqual(mockUserData); }); it('should log debug message when starting password authentication', async () => { const context = createContext('test@example.com', 'password123'); vi.mocked(mockUserDataRepository.loadUserData).mockResolvedValue(undefined); vi.mocked(mockAuthService.loginWithPassword).mockResolvedValue(mockUserData); vi.mocked(mockUserDataRepository.saveUserData).mockResolvedValue(); await strategy.authenticate(context); expect(mockLogger.debug).toHaveBeenCalledWith('Authenticating with password for user:', 'test@example.com'); }); it('should return undefined when password authentication fails', async () => { const context = createContext('test@example.com', 'wrong-password'); vi.mocked(mockUserDataRepository.loadUserData).mockResolvedValue(undefined); vi.mocked(mockAuthService.loginWithPassword).mockResolvedValue(undefined); vi.mocked(mockUserDataRepository.saveUserData).mockResolvedValue(); const result = await strategy.authenticate(context); expect(mockAuthService.loginWithPassword).toHaveBeenCalledWith('test@example.com', 'wrong-password'); expect(mockUserDataRepository.saveUserData).toHaveBeenCalledWith(undefined); expect(mockLogger.notice).toHaveBeenCalledWith('Authentication failed!'); expect(result).toBeUndefined(); }); it('should save new user data after successful authentication', async () => { const context = createContext('test@example.com', 'password123'); vi.mocked(mockUserDataRepository.loadUserData).mockResolvedValue(undefined); vi.mocked(mockAuthService.loginWithPassword).mockResolvedValue(mockUserData); vi.mocked(mockUserDataRepository.saveUserData).mockResolvedValue(); await strategy.authenticate(context); expect(mockUserDataRepository.saveUserData).toHaveBeenCalledWith(mockUserData); }); it('should continue when save fails but log warning', async () => { const context = createContext('test@example.com', 'password123'); const saveError = new Error('Save failed'); vi.mocked(mockUserDataRepository.loadUserData).mockResolvedValue(undefined); vi.mocked(mockAuthService.loginWithPassword).mockResolvedValue(mockUserData); vi.mocked(mockUserDataRepository.saveUserData).mockRejectedValue(saveError); const result = await strategy.authenticate(context); expect(mockLogger.warn).toHaveBeenCalledWith('Failed to save user data, but login succeeded:', saveError); expect(mockLogger.notice).toHaveBeenCalledWith('Authentication successful!'); expect(result).toEqual(mockUserData); }); it('should handle non-Error objects in save failures', async () => { const context = createContext('test@example.com', 'password123'); vi.mocked(mockUserDataRepository.loadUserData).mockResolvedValue(undefined); vi.mocked(mockAuthService.loginWithPassword).mockResolvedValue(mockUserData); vi.mocked(mockUserDataRepository.saveUserData).mockRejectedValue('String save error'); const result = await strategy.authenticate(context); expect(mockLogger.warn).toHaveBeenCalledWith('Failed to save user data, but login succeeded:', 'String save error'); expect(result).toEqual(mockUserData); }); it('should use password from context', async () => { const context = createContext('user@test.com', 'my-secret-password'); vi.mocked(mockUserDataRepository.loadUserData).mockResolvedValue(undefined); vi.mocked(mockAuthService.loginWithPassword).mockResolvedValue(mockUserData); vi.mocked(mockUserDataRepository.saveUserData).mockResolvedValue(); await strategy.authenticate(context); expect(mockAuthService.loginWithPassword).toHaveBeenCalledWith('user@test.com', 'my-secret-password'); }); }); describe('integration scenarios', () => { it('should handle complete flow from cached token failure to successful password auth', async () => { const context = createContext('test@example.com', 'password123'); const oldUserData = { ...mockUserData, token: 'old-token' }; vi.mocked(mockUserDataRepository.loadUserData).mockResolvedValue(oldUserData); vi.mocked(mockAuthService.loginWithCachedToken).mockRejectedValue(new Error('Token expired')); vi.mocked(mockUserDataRepository.clearUserData).mockResolvedValue(); vi.mocked(mockAuthService.loginWithPassword).mockResolvedValue(mockUserData); vi.mocked(mockUserDataRepository.saveUserData).mockResolvedValue(); const result = await strategy.authenticate(context); expect(mockUserDataRepository.loadUserData).toHaveBeenCalledWith('test@example.com'); expect(mockAuthService.loginWithCachedToken).toHaveBeenCalledWith('test@example.com', oldUserData); expect(mockUserDataRepository.clearUserData).toHaveBeenCalled(); expect(mockAuthService.loginWithPassword).toHaveBeenCalledWith('test@example.com', 'password123'); expect(mockUserDataRepository.saveUserData).toHaveBeenCalledWith(mockUserData); expect(result).toEqual(mockUserData); }); it('should attempt to save even when password authentication returns undefined', async () => { const context = createContext('test@example.com', 'wrong-password'); vi.mocked(mockUserDataRepository.loadUserData).mockResolvedValue(undefined); vi.mocked(mockAuthService.loginWithPassword).mockResolvedValue(undefined); vi.mocked(mockUserDataRepository.saveUserData).mockResolvedValue(); await strategy.authenticate(context); expect(mockUserDataRepository.saveUserData).toHaveBeenCalledWith(undefined); }); }); }); }); //# sourceMappingURL=PasswordAuthStrategy.test.js.map