matterbridge-roborock-vacuum-plugin
Version:
Matterbridge Roborock Vacuum Plugin
157 lines • 7.87 kB
JavaScript
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { RoborockAuthGateway } from '../../../roborockCommunication/adapters/RoborockAuthGateway.js';
import { asType, createMockLogger } from '../../testUtils.js';
// Mock the authenticate API
const mockAuthApi = {
requestCodeV4: vi.fn(),
loginWithCodeV4: vi.fn(),
loginWithPassword: vi.fn(),
loginWithUserData: vi.fn(),
getHomeDetails: vi.fn(),
};
describe('RoborockAuthGateway', () => {
let authGateway;
let mockLogger;
beforeEach(() => {
mockLogger = createMockLogger();
authGateway = new RoborockAuthGateway(asType(mockAuthApi), mockLogger);
// Reset all mocks
vi.clearAllMocks();
});
describe('requestVerificationCode', () => {
it('should successfully request verification code', async () => {
mockAuthApi.requestCodeV4.mockResolvedValue(undefined);
await authGateway.requestVerificationCode('test@example.com');
expect(mockAuthApi.requestCodeV4).toHaveBeenCalledWith('test@example.com');
expect(mockLogger.info).toHaveBeenCalledWith('Requesting verification code for test@example.com');
});
it('should handle API errors gracefully', async () => {
const apiError = new Error('Network timeout');
mockAuthApi.requestCodeV4.mockRejectedValue(apiError);
await expect(authGateway.requestVerificationCode('test@example.com')).rejects.toThrow(Error);
});
});
describe('loginWithVerificationCode', () => {
const mockUserData = {
username: 'test@example.com',
uid: 'test-uid',
token: 'test-token',
rruid: 'test-rruid',
region: 'us',
countrycode: 'US',
};
it('should login successfully with verification code', async () => {
mockAuthApi.loginWithCodeV4.mockResolvedValue(mockUserData);
const result = await authGateway.authenticate2FA('test@example.com', '123456');
expect(mockAuthApi.loginWithCodeV4).toHaveBeenCalledWith('test@example.com', '123456');
expect(result).toBe(mockUserData);
expect(mockLogger.info).toHaveBeenCalledWith('2FA Authentication successful');
});
it('should handle network errors during code login', async () => {
mockAuthApi.loginWithCodeV4.mockRejectedValue(new Error('Connection failed'));
await expect(authGateway.authenticate2FA('test@example.com', '123456')).rejects.toThrow(Error);
});
});
describe('loginWithPassword', () => {
const mockUserData = {
username: 'test@example.com',
uid: 'test-uid',
token: 'test-token',
rruid: 'test-rruid',
region: 'us',
countrycode: 'US',
};
it('should use cached user data when available', async () => {
mockAuthApi.loginWithPassword.mockResolvedValue(mockUserData);
const result = await authGateway.authenticatePassword('test@example.com', 'password123');
expect(mockAuthApi.loginWithPassword).toHaveBeenCalledWith('test@example.com', 'password123');
expect(result).toBe(mockUserData);
expect(mockLogger.info).toHaveBeenCalledWith('Password authentication successful');
});
});
describe('refreshToken', () => {
const mockCachedData = {
username: 'test@example.com',
uid: 'cached-uid',
token: 'cached-token',
rruid: 'cached-rruid',
region: 'eu',
countrycode: 'DE',
};
it('should validate and refresh cached token', async () => {
const refreshedData = { ...mockCachedData, token: 'refreshed-token' };
mockAuthApi.loginWithUserData.mockResolvedValue(refreshedData);
const result = await authGateway.refreshToken(mockCachedData);
expect(mockAuthApi.loginWithUserData).toHaveBeenCalledWith('test@example.com', mockCachedData);
expect(result).toEqual(refreshedData);
expect(mockLogger.info).toHaveBeenCalledWith('Token refreshed successfully');
});
it('should handle expired cached tokens', async () => {
mockAuthApi.loginWithUserData.mockRejectedValue(new Error('Token expired'));
await expect(authGateway.refreshToken(mockCachedData)).rejects.toThrow(Error);
});
it('should validate cached data structure', async () => {
const invalidData = { uid: 'test' };
mockAuthApi.loginWithUserData.mockRejectedValue(new Error('Invalid user data'));
await expect(authGateway.refreshToken(invalidData)).rejects.toThrow(Error);
});
it('should handle network errors during token validation', async () => {
mockAuthApi.loginWithUserData.mockRejectedValue(new Error('Network unreachable'));
await expect(authGateway.refreshToken(mockCachedData)).rejects.toThrow(Error);
});
});
describe('error handling and edge cases', () => {
it('should handle API rate limiting', async () => {
mockAuthApi.requestCodeV4.mockRejectedValue(new Error('Rate limit exceeded'));
await expect(authGateway.requestVerificationCode('test@example.com')).rejects.toThrow(Error);
});
it('should handle malformed API responses', async () => {
mockAuthApi.loginWithPassword.mockResolvedValue(null);
const result = await authGateway.authenticatePassword('test@example.com', 'password123');
expect(result).toBeNull();
});
it('should preserve original error context', async () => {
const originalError = new Error('Specific API error');
originalError.stack = 'Original stack trace';
mockAuthApi.loginWithPassword.mockRejectedValue(originalError);
let caughtError;
try {
await authGateway.authenticatePassword('test@example.com', 'password123');
}
catch (error) {
caughtError = error;
}
expect(caughtError).toBeInstanceOf(Error);
expect(caughtError.message).toBe('Specific API error');
});
});
describe('integration scenarios', () => {
it('should handle complete authentication workflow', async () => {
const userData = {
uid: 'workflow-uid',
token: 'workflow-token',
rruid: 'workflow-rruid',
region: 'us',
countrycode: 'US',
};
// Step 1: Request verification code
mockAuthApi.requestCodeV4.mockResolvedValue(undefined);
await authGateway.requestVerificationCode('test@example.com');
// Step 2: Login with verification code
mockAuthApi.loginWithCodeV4.mockResolvedValue(userData);
const result = await authGateway.authenticate2FA('test@example.com', '123456');
expect(result).toBe(userData);
});
it('should handle authentication retry scenarios', async () => {
const userData = { uid: 'retry-uid', token: 'retry-token' };
// First attempt fails
mockAuthApi.loginWithPassword.mockRejectedValueOnce(new Error('Network error')).mockResolvedValueOnce(userData);
// Should throw on first attempt
await expect(authGateway.authenticatePassword('test@example.com', 'password123')).rejects.toThrow(Error);
// Second attempt should succeed
const result = await authGateway.authenticatePassword('test@example.com', 'password123');
expect(result).toBe(userData);
});
});
});
//# sourceMappingURL=roborockAuthGateway.test.js.map