matterbridge-roborock-vacuum-plugin
Version:
Matterbridge Roborock Vacuum Plugin
310 lines • 15.6 kB
JavaScript
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { AreaManagementService } from '../../services/areaManagementService.js';
import { AuthenticationCoordinator } from '../../services/authentication/AuthenticationCoordinator.js';
import { DeviceManagementService } from '../../services/deviceManagementService.js';
import { MessageRoutingService } from '../../services/messageRoutingService.js';
import { ServiceContainer } from '../../services/serviceContainer.js';
import { localStorageMock } from '../testData/localStorageMock.js';
import { asPartial, createMockAuthApi, createMockIotApi, createMockLocalStorage, makeLogger, makeMockClientRouter, } from '../testUtils.js';
describe('ServiceContainer', () => {
let container;
let mockLogger;
let mockAuthApi;
let mockIotApi;
let config;
let mockUserData;
beforeEach(() => {
mockLogger = makeLogger();
mockAuthApi = createMockAuthApi();
mockIotApi = createMockIotApi();
mockUserData = {
username: 'test-user',
uid: 'test-uid',
tokentype: 'Bearer',
token: 'test-token',
rruid: 'rr-uid',
region: 'us',
countrycode: 'US',
country: 'United States',
nickname: 'Test User',
rriot: {
u: 'test-user',
s: 'test-secret',
h: 'test-host',
k: 'test-key',
r: { a: 'test-region-a', m: 'test-mqtt', r: '', l: '' },
},
};
config = {
baseUrl: 'https://test.roborock.com',
refreshInterval: 30000,
authenticateApiFactory: vi.fn(() => mockAuthApi),
iotApiFactory: vi.fn(() => mockIotApi),
persist: createMockLocalStorage({ storage: localStorageMock }),
configManager: asPartial({}),
toastMessage: vi.fn(),
};
container = new ServiceContainer(mockLogger, config);
});
afterEach(async () => {
await container.destroy();
vi.clearAllMocks();
});
describe('initialization', () => {
it('should create container with provided dependencies', () => {
expect(container).toBeInstanceOf(ServiceContainer);
expect(container.getLogger()).toBe(mockLogger);
expect(container.getClientManager()).not.toBeUndefined();
});
it('should use custom factories when provided', () => {
expect(config.authenticateApiFactory).toHaveBeenCalledWith(mockLogger, config.baseUrl);
});
it('should create default factories when not provided', async () => {
const defaultConfig = {
baseUrl: 'https://default.com',
refreshInterval: 10000,
persist: {},
configManager: asPartial({}),
toastMessage: vi.fn(),
};
const defaultContainer = new ServiceContainer(mockLogger, defaultConfig);
expect(defaultContainer).toBeInstanceOf(ServiceContainer);
await defaultContainer.destroy();
});
});
describe('setUserData', () => {
it('should set user data and create IoT API', () => {
container.setUserData(mockUserData);
expect(config.iotApiFactory).toHaveBeenCalledWith(mockLogger, mockUserData);
expect(container.getUserData()).toBe(mockUserData);
});
it('should update existing DeviceManagementService when user data is set', () => {
const deviceService = container.getDeviceManagementService();
const setAuthSpy = vi.spyOn(deviceService, 'setAuthentication');
container.setUserData(mockUserData);
expect(setAuthSpy).toHaveBeenCalledWith(mockUserData);
});
it('should update existing AreaManagementService when user data is set', () => {
const areaService = container.getAreaManagementService();
const setIotApiSpy = vi.spyOn(areaService, 'setIotApi');
container.setUserData(mockUserData);
expect(setIotApiSpy).toHaveBeenCalledWith(mockIotApi);
});
it('should update existing MessageRoutingService when user data is set', () => {
const messageService = container.getMessageRoutingService();
const setIotApiSpy = vi.spyOn(messageService, 'setIotApi');
container.setUserData(mockUserData);
expect(setIotApiSpy).toHaveBeenCalledWith(mockIotApi);
});
});
describe('getAuthenticationCoordinator', () => {
it('should create and return AuthenticationCoordinator singleton', () => {
const service1 = container.getAuthenticationCoordinator();
const service2 = container.getAuthenticationCoordinator();
expect(service1).toBeInstanceOf(AuthenticationCoordinator);
expect(service1).toBe(service2); // Same instance
});
it('should initialize AuthenticationCoordinator with correct dependencies', () => {
const service = container.getAuthenticationCoordinator();
expect(service).toBeDefined();
});
});
describe('getDeviceManagementService', () => {
it('should create and return DeviceManagementService singleton', () => {
const service1 = container.getDeviceManagementService();
const service2 = container.getDeviceManagementService();
expect(service1).toBeInstanceOf(DeviceManagementService);
expect(service1).toBe(service2); // Same instance
});
it('should initialize DeviceManagementService with correct dependencies', () => {
const service = container.getDeviceManagementService();
expect(service).toBeDefined();
});
});
describe('getAreaManagementService', () => {
it('should create and return AreaManagementService singleton', () => {
const service1 = container.getAreaManagementService();
const service2 = container.getAreaManagementService();
expect(service1).toBeInstanceOf(AreaManagementService);
expect(service1).toBe(service2); // Same instance
});
it('should initialize AreaManagementService with correct dependencies', () => {
const service = container.getAreaManagementService();
expect(service).toBeDefined();
});
});
describe('getMessageRoutingService', () => {
it('should create and return MessageRoutingService singleton', () => {
const service1 = container.getMessageRoutingService();
const service2 = container.getMessageRoutingService();
expect(service1).toBeInstanceOf(MessageRoutingService);
expect(service1).toBe(service2); // Same instance
});
it('should initialize MessageRoutingService with correct dependencies', () => {
const service = container.getMessageRoutingService();
expect(service).toBeDefined();
});
});
describe('getPollingService', () => {
it('should create and return PollingService singleton', () => {
const service1 = container.getPollingService();
const service2 = container.getPollingService();
expect(service1).toBeDefined();
expect(service1).toBe(service2); // Same instance
});
it('should initialize PollingService with refresh interval from config', () => {
const service = container.getPollingService();
expect(service).toBeDefined();
});
});
describe('getConnectionService', () => {
it('should create and return ConnectionService singleton', () => {
const service1 = container.getConnectionService();
const service2 = container.getConnectionService();
expect(service1).toBeDefined();
expect(service1).toBe(service2); // Same instance
});
});
describe('synchronizeMessageClients', () => {
it('should synchronize message clients when clientRouter is initialized', () => {
const connectionService = container.getConnectionService();
const areaService = container.getAreaManagementService();
// Mock clientRouter to be defined
const mockClientRouter = makeMockClientRouter();
connectionService.clientRouter = asPartial(mockClientRouter);
const setMessageClientSpy = vi.spyOn(areaService, 'setMessageClient');
container.synchronizeMessageClients();
expect(setMessageClientSpy).toHaveBeenCalledWith(mockClientRouter);
});
it('should throw error when ConnectionService is not initialized', () => {
expect(() => container.synchronizeMessageClients()).toThrow('Message client not initialized in ConnectionService');
});
it('should throw error when clientRouter is undefined', () => {
container.getConnectionService();
expect(() => container.synchronizeMessageClients()).toThrow('Message client not initialized in ConnectionService');
});
});
describe('getIotApi', () => {
it('should return undefined when user is not authenticated', () => {
expect(container.getIotApi()).toBeUndefined();
});
it('should return IoT API after user authentication', () => {
container.setUserData(mockUserData);
const iotApi = container.getIotApi();
expect(iotApi).toBe(mockIotApi);
});
});
describe('getAllServices', () => {
it('should return all services in a bundle', () => {
const services = container.getAllServices();
expect(services).toHaveProperty('authenticationCoordinator');
expect(services).toHaveProperty('deviceManagement');
expect(services).toHaveProperty('areaManagement');
expect(services).toHaveProperty('messageRouting');
expect(services).toHaveProperty('polling');
expect(services).toHaveProperty('connection');
expect(services.authenticationCoordinator).toBeInstanceOf(AuthenticationCoordinator);
expect(services.deviceManagement).toBeInstanceOf(DeviceManagementService);
expect(services.areaManagement).toBeInstanceOf(AreaManagementService);
expect(services.messageRouting).toBeInstanceOf(MessageRoutingService);
});
it('should return same instances on multiple calls', () => {
const services1 = container.getAllServices();
const services2 = container.getAllServices();
expect(services1.authenticationCoordinator).toBe(services2.authenticationCoordinator);
expect(services1.deviceManagement).toBe(services2.deviceManagement);
expect(services1.areaManagement).toBe(services2.areaManagement);
expect(services1.messageRouting).toBe(services2.messageRouting);
expect(services1.polling).toBe(services2.polling);
expect(services1.connection).toBe(services2.connection);
});
});
describe('destroy', () => {
it('should clear all service references', async () => {
// Create all services first
container.getAuthenticationCoordinator();
container.getDeviceManagementService();
container.getAreaManagementService();
container.getMessageRoutingService();
container.getPollingService();
container.getConnectionService();
await container.destroy();
expect(mockLogger.debug).toHaveBeenCalledWith('ServiceContainer destroyed');
});
it('should clear user data', async () => {
container.setUserData(mockUserData);
expect(container.getUserData()).toBe(mockUserData);
await container.destroy();
expect(container.getUserData()).toBeUndefined();
});
it('should create new instances after destroy', async () => {
const service1 = container.getAuthenticationCoordinator();
await container.destroy();
const service2 = container.getAuthenticationCoordinator();
expect(service1).not.toBe(service2); // Different instances
});
it('should shutdown polling service before clearing references', async () => {
const pollingService = container.getPollingService();
const shutdownSpy = vi.spyOn(pollingService, 'shutdown');
await container.destroy();
expect(shutdownSpy).toHaveBeenCalled();
});
it('should clear IoT API on destroy', async () => {
container.setUserData(mockUserData);
expect(container.getIotApi()).toBeDefined();
await container.destroy();
expect(container.getIotApi()).toBeUndefined();
});
});
describe('getLogger', () => {
it('should return the logger instance', () => {
const logger = container.getLogger();
expect(logger).toBe(mockLogger);
});
});
describe('getClientManager', () => {
it('should return the ClientManager instance', () => {
const clientManager = container.getClientManager();
expect(clientManager).not.toBeUndefined();
});
});
describe('getUserData', () => {
it('should return undefined when not authenticated', () => {
expect(container.getUserData()).toBeUndefined();
});
it('should return user data after authentication', () => {
container.setUserData(mockUserData);
expect(container.getUserData()).toBe(mockUserData);
});
});
describe('integration scenarios', () => {
it('should support complete workflow: login -> set user data -> get services', () => {
// 1. Get authentication coordinator
const authCoordinator = container.getAuthenticationCoordinator();
expect(authCoordinator).toBeInstanceOf(AuthenticationCoordinator);
// 2. Simulate successful login
container.setUserData(mockUserData);
// 3. Get device services
const services = container.getAllServices();
expect(services.deviceManagement).toBeInstanceOf(DeviceManagementService);
expect(services.areaManagement).toBeInstanceOf(AreaManagementService);
expect(services.messageRouting).toBeInstanceOf(MessageRoutingService);
// 4. Verify user data is set
expect(container.getUserData()).toBe(mockUserData);
});
it('should handle service creation before authentication', () => {
// Create services before setting user data
const deviceService = container.getDeviceManagementService();
const areaService = container.getAreaManagementService();
const messageService = container.getMessageRoutingService();
expect(deviceService).toBeInstanceOf(DeviceManagementService);
expect(areaService).toBeInstanceOf(AreaManagementService);
expect(messageService).toBeInstanceOf(MessageRoutingService);
// Now authenticate
container.setUserData(mockUserData);
// Services should be updated
expect(container.getUserData()).toBe(mockUserData);
});
});
});
//# sourceMappingURL=serviceContainer.test.js.map