mirror of
https://github.com/Koenkk/zigbee2mqtt.git
synced 2024-11-17 02:48:31 -07:00
ba7a85bbb5
* Initial implementation of backend for frontend. * Frontend fixes (#4205) * Send data to frontend api withoud baseTopic preffix * Send frontend api requests to mqtt * Fix topic name sanitisation * Fix base_topic trimming * Update frontend.js * Add zigbee2mqtt-frontend dependency Co-authored-by: Koen Kanters <koenkanters94@gmail.com> * Dont' setup separate MQTT connection. * Correctly stop * Add frontend tests. * [WIP] Bindings structure change (#4233) * Change bindings location * Bump frontend version * Republish devices on bindings change * Fix data structure * Fix payload double encoding * Change endpoints structure * Expose config to bridge/info * Fix typo * Updates Co-authored-by: Koen Kanters <koenkanters94@gmail.com> * Resend states on ws reconnect * Update deps * Bump frontend (#4264) Co-authored-by: John Doe <nurikk@users.noreply.github.com>
195 lines
7.1 KiB
JavaScript
195 lines
7.1 KiB
JavaScript
const data = require('./stub/data');
|
|
require('./stub/logger');
|
|
require('./stub/zigbeeHerdsman');
|
|
const MQTT = require('./stub/mqtt');
|
|
const settings = require('../lib/util/settings');
|
|
const Controller = require('../lib/controller');
|
|
const stringify = require('json-stable-stringify');
|
|
const flushPromises = () => new Promise(setImmediate);
|
|
jest.spyOn(process, 'exit').mockImplementation(() => {});
|
|
|
|
const mockHTTP = {
|
|
implementation: {
|
|
listen: jest.fn(),
|
|
on: (event, handler) => {mockHTTP.events[event] = handler},
|
|
close: jest.fn().mockImplementation((cb) => cb()),
|
|
},
|
|
variables: {},
|
|
events: {},
|
|
};
|
|
|
|
const mockHTTPProxy = {
|
|
implementation: {
|
|
web: jest.fn(),
|
|
ws: jest.fn(),
|
|
},
|
|
variables: {},
|
|
events: {},
|
|
};
|
|
|
|
const mockWS = {
|
|
implementation: {
|
|
clients: [],
|
|
on: (event, handler) => {mockWS.events[event] = handler},
|
|
handleUpgrade: jest.fn(),
|
|
emit: jest.fn(),
|
|
},
|
|
variables: {},
|
|
events: {},
|
|
};
|
|
|
|
const mockNodeStatic = {
|
|
implementation: {
|
|
serve: jest.fn(),
|
|
},
|
|
variables: {},
|
|
events: {},
|
|
};
|
|
|
|
jest.mock('http', () => ({
|
|
createServer: jest.fn().mockImplementation((onRequest) => {
|
|
mockHTTP.variables.onRequest = onRequest;
|
|
return mockHTTP.implementation;
|
|
}),
|
|
}));
|
|
|
|
jest.mock('http-proxy', () => ({
|
|
createProxyServer: jest.fn().mockImplementation((initParameter) => {
|
|
mockHTTPProxy.variables.initParameter = initParameter;
|
|
return mockHTTPProxy.implementation;
|
|
}),
|
|
}));
|
|
|
|
jest.mock('node-static', () => ({
|
|
Server: jest.fn().mockImplementation((path) => {
|
|
mockNodeStatic.variables.path = path;
|
|
return mockNodeStatic.implementation;
|
|
}),
|
|
}));
|
|
|
|
jest.mock('zigbee2mqtt-frontend', () => ({
|
|
getPath: () => 'my/dummy/path',
|
|
}));
|
|
|
|
jest.mock('ws', () => ({
|
|
OPEN: 'open',
|
|
Server: jest.fn().mockImplementation(() => {
|
|
return mockWS.implementation;
|
|
}),
|
|
}));
|
|
|
|
describe('Frontend', () => {
|
|
let controller;
|
|
|
|
beforeEach(async () => {
|
|
mockWS.implementation.clients = [];
|
|
data.writeDefaultConfiguration();
|
|
data.writeDefaultState();
|
|
settings._reRead();
|
|
settings.set(['experimental'], {new_api: true, frontend: {port: 8081}});
|
|
});
|
|
|
|
it('Start/stop', async () => {
|
|
controller = new Controller();
|
|
await controller.start();
|
|
expect(mockNodeStatic.variables.path).toBe("my/dummy/path");
|
|
expect(mockHTTP.implementation.listen).toHaveBeenCalledWith(8081);
|
|
|
|
const mockWSClient = {
|
|
implementation: {
|
|
close: jest.fn(),
|
|
},
|
|
events: {},
|
|
};
|
|
mockWS.implementation.clients.push(mockWSClient.implementation);
|
|
await controller.stop();
|
|
expect(mockWSClient.implementation.close).toHaveBeenCalledTimes(1);
|
|
expect(mockHTTP.implementation.close).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('Websocket interaction', async () => {
|
|
controller = new Controller();
|
|
await controller.start();
|
|
|
|
// Connect
|
|
const mockWSClient = {
|
|
implementation: {
|
|
on: (event, handler) => {mockWSClient.events[event] = handler},
|
|
send: jest.fn(),
|
|
readyState: 'open',
|
|
},
|
|
events: {},
|
|
};
|
|
mockWS.implementation.clients.push(mockWSClient.implementation);
|
|
await mockWS.events.connection(mockWSClient.implementation);
|
|
expect(mockWSClient.implementation.send).toHaveBeenCalledTimes(9);
|
|
expect(JSON.parse(mockWSClient.implementation.send.mock.calls[0])).toStrictEqual({topic: 'bridge/state', payload: 'online'});
|
|
expect(JSON.parse(mockWSClient.implementation.send.mock.calls[8])).toStrictEqual({topic:"remote", payload:{brightness:255}});
|
|
|
|
// Message
|
|
MQTT.publish.mockClear();
|
|
mockWSClient.implementation.send.mockClear();
|
|
mockWSClient.events.message(stringify({topic: 'bulb_color/set', payload: {state: 'ON'}}))
|
|
await flushPromises();
|
|
expect(MQTT.publish).toHaveBeenCalledTimes(1);
|
|
expect(MQTT.publish).toHaveBeenCalledWith(
|
|
'zigbee2mqtt/bulb_color',
|
|
stringify({state: 'ON'}),
|
|
{ retain: false, qos: 0 },
|
|
expect.any(Function)
|
|
);
|
|
|
|
// Received message on socket
|
|
expect(mockWSClient.implementation.send).toHaveBeenCalledTimes(1);
|
|
expect(mockWSClient.implementation.send).toHaveBeenCalledWith(stringify({topic: 'bulb_color', payload: {state: 'ON'}}));
|
|
|
|
// Shouldnt set when not ready
|
|
mockWSClient.implementation.send.mockClear();
|
|
mockWSClient.implementation.readyState = 'close';
|
|
mockWSClient.events.message(stringify({topic: 'bulb_color/set', payload: {state: 'ON'}}))
|
|
expect(mockWSClient.implementation.send).toHaveBeenCalledTimes(0);
|
|
});
|
|
|
|
it('onReques/onUpgrade', async () => {
|
|
controller = new Controller();
|
|
await controller.start();
|
|
|
|
const mockSocket = {destroy: jest.fn()};
|
|
mockWS.implementation.handleUpgrade.mockClear();
|
|
mockHTTP.events.upgrade({url: 'http://localhost:8080/api'}, mockSocket, 3);
|
|
expect(mockWS.implementation.handleUpgrade).toHaveBeenCalledTimes(1);
|
|
expect(mockSocket.destroy).toHaveBeenCalledTimes(0);
|
|
expect(mockWS.implementation.handleUpgrade).toHaveBeenCalledWith({"url": "http://localhost:8080/api"}, mockSocket, 3, expect.any(Function));
|
|
mockWS.implementation.handleUpgrade.mock.calls[0][3](99);
|
|
expect(mockWS.implementation.emit).toHaveBeenCalledWith('connection', 99, {"url": "http://localhost:8080/api"});
|
|
|
|
mockWS.implementation.handleUpgrade.mockClear();
|
|
mockHTTP.events.upgrade({url: 'http://localhost:8080/unkown'}, mockSocket, 3);
|
|
expect(mockWS.implementation.handleUpgrade).toHaveBeenCalledTimes(0);
|
|
expect(mockSocket.destroy).toHaveBeenCalledTimes(1);
|
|
|
|
mockHTTP.variables.onRequest(1, 2);
|
|
expect(mockNodeStatic.implementation.serve).toHaveBeenCalledTimes(1);
|
|
expect(mockNodeStatic.implementation.serve).toHaveBeenCalledWith(1, 2);
|
|
});
|
|
|
|
it('Development server', async () => {
|
|
settings.set(['experimental', 'frontend'], {development_server: 'localhost:3001'});
|
|
controller = new Controller();
|
|
await controller.start();
|
|
expect(mockHTTPProxy.variables.initParameter).toStrictEqual({ws: true});
|
|
expect(mockHTTP.implementation.listen).toHaveBeenCalledWith(8080);
|
|
|
|
mockHTTP.variables.onRequest(1, 2);
|
|
expect(mockHTTPProxy.implementation.web).toHaveBeenCalledTimes(1);
|
|
expect(mockHTTPProxy.implementation.web).toHaveBeenCalledWith(1, 2, {"target": "http://localhost:3001"});
|
|
|
|
const mockSocket = {destroy: jest.fn()};
|
|
mockHTTPProxy.implementation.ws.mockClear();
|
|
mockHTTP.events.upgrade({url: 'http://localhost:8080/sockjs-node'}, mockSocket, 3);
|
|
expect(mockHTTPProxy.implementation.ws).toHaveBeenCalledTimes(1);
|
|
expect(mockSocket.destroy).toHaveBeenCalledTimes(0);
|
|
expect(mockHTTPProxy.implementation.ws).toHaveBeenCalledWith({"url": "http://localhost:8080/sockjs-node"}, mockSocket, 3, {"target": "ws://localhost:3001"});
|
|
});
|
|
});
|