2020-09-04 09:42:24 -07:00
|
|
|
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');
|
2020-09-24 09:06:43 -07:00
|
|
|
const stringify = require('json-stable-stringify-without-jsonify');
|
2021-07-05 11:46:53 -07:00
|
|
|
const flushPromises = require('./lib/flushPromises');
|
2020-11-13 09:29:23 -07:00
|
|
|
const zigbeeHerdsman = require('./stub/zigbeeHerdsman');
|
2020-09-04 09:42:24 -07:00
|
|
|
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: {},
|
|
|
|
};
|
|
|
|
|
2020-11-25 08:28:02 -07:00
|
|
|
const mockWSocket = {
|
|
|
|
close: jest.fn(),
|
2020-09-04 09:42:24 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
const mockWS = {
|
|
|
|
implementation: {
|
|
|
|
clients: [],
|
|
|
|
on: (event, handler) => {mockWS.events[event] = handler},
|
2020-11-25 08:28:02 -07:00
|
|
|
handleUpgrade: jest.fn().mockImplementation((request, socket, head, cb) => {
|
|
|
|
cb(mockWSocket)
|
|
|
|
}),
|
2020-09-04 09:42:24 -07:00
|
|
|
emit: jest.fn(),
|
2021-08-10 10:12:13 -07:00
|
|
|
close: jest.fn(),
|
2020-09-04 09:42:24 -07:00
|
|
|
},
|
|
|
|
variables: {},
|
|
|
|
events: {},
|
|
|
|
};
|
|
|
|
|
|
|
|
const mockNodeStatic = {
|
2020-09-24 09:06:43 -07:00
|
|
|
implementation: jest.fn(),
|
2020-09-04 09:42:24 -07:00
|
|
|
variables: {},
|
|
|
|
events: {},
|
|
|
|
};
|
|
|
|
|
|
|
|
jest.mock('http', () => ({
|
|
|
|
createServer: jest.fn().mockImplementation((onRequest) => {
|
|
|
|
mockHTTP.variables.onRequest = onRequest;
|
|
|
|
return mockHTTP.implementation;
|
|
|
|
}),
|
|
|
|
}));
|
|
|
|
|
2020-09-24 09:06:43 -07:00
|
|
|
jest.mock("serve-static", () =>
|
|
|
|
jest.fn().mockImplementation((path) => {
|
|
|
|
mockNodeStatic.variables.path = path
|
|
|
|
return mockNodeStatic.implementation
|
|
|
|
})
|
|
|
|
);
|
2020-09-04 09:42:24 -07:00
|
|
|
|
|
|
|
jest.mock('zigbee2mqtt-frontend', () => ({
|
|
|
|
getPath: () => 'my/dummy/path',
|
|
|
|
}));
|
|
|
|
|
|
|
|
jest.mock('ws', () => ({
|
|
|
|
OPEN: 'open',
|
|
|
|
Server: jest.fn().mockImplementation(() => {
|
|
|
|
return mockWS.implementation;
|
|
|
|
}),
|
|
|
|
}));
|
|
|
|
|
|
|
|
describe('Frontend', () => {
|
|
|
|
let controller;
|
|
|
|
|
2021-07-05 11:46:53 -07:00
|
|
|
beforeAll(async () => {
|
|
|
|
jest.useFakeTimers();
|
|
|
|
});
|
|
|
|
|
2020-09-04 09:42:24 -07:00
|
|
|
beforeEach(async () => {
|
|
|
|
mockWS.implementation.clients = [];
|
|
|
|
data.writeDefaultConfiguration();
|
|
|
|
data.writeDefaultState();
|
2021-03-09 11:50:05 -07:00
|
|
|
settings.reRead();
|
2020-11-23 11:09:47 -07:00
|
|
|
settings.set(['frontend'], {port: 8081, host: "127.0.0.1"});
|
2020-09-10 05:54:03 -07:00
|
|
|
settings.set(['homeassistant'], true);
|
2020-11-13 09:29:23 -07:00
|
|
|
zigbeeHerdsman.devices.bulb.linkquality = 10;
|
|
|
|
});
|
|
|
|
|
2021-07-05 11:46:53 -07:00
|
|
|
afterAll(async () => {
|
|
|
|
jest.useRealTimers();
|
|
|
|
});
|
|
|
|
|
2020-11-13 09:29:23 -07:00
|
|
|
afterEach(async() => {
|
|
|
|
delete zigbeeHerdsman.devices.bulb.linkquality;
|
2020-09-04 09:42:24 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
it('Start/stop', async () => {
|
2021-02-06 08:32:20 -07:00
|
|
|
controller = new Controller(jest.fn(), jest.fn());
|
2020-09-04 09:42:24 -07:00
|
|
|
await controller.start();
|
|
|
|
expect(mockNodeStatic.variables.path).toBe("my/dummy/path");
|
2020-11-23 11:09:47 -07:00
|
|
|
expect(mockHTTP.implementation.listen).toHaveBeenCalledWith(8081, "127.0.0.1");
|
2020-09-04 09:42:24 -07:00
|
|
|
|
|
|
|
const mockWSClient = {
|
|
|
|
implementation: {
|
2021-08-10 10:12:13 -07:00
|
|
|
terminate: jest.fn(),
|
2021-02-06 10:29:36 -07:00
|
|
|
send: jest.fn(),
|
2020-09-04 09:42:24 -07:00
|
|
|
},
|
|
|
|
events: {},
|
|
|
|
};
|
|
|
|
mockWS.implementation.clients.push(mockWSClient.implementation);
|
|
|
|
await controller.stop();
|
2021-08-10 10:12:13 -07:00
|
|
|
expect(mockWSClient.implementation.terminate).toHaveBeenCalledTimes(1);
|
2020-09-04 09:42:24 -07:00
|
|
|
expect(mockHTTP.implementation.close).toHaveBeenCalledTimes(1);
|
2021-08-10 10:12:13 -07:00
|
|
|
expect(mockWS.implementation.close).toHaveBeenCalledTimes(1);
|
2020-09-04 09:42:24 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
it('Websocket interaction', async () => {
|
2021-02-06 08:32:20 -07:00
|
|
|
controller = new Controller(jest.fn(), jest.fn());
|
2020-09-04 09:42:24 -07:00
|
|
|
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);
|
2020-10-07 08:05:46 -07:00
|
|
|
|
2021-02-22 06:29:58 -07:00
|
|
|
expect(mockWSClient.implementation.send).toHaveBeenCalledWith(stringify({topic: 'bridge/state', payload: 'online'}));
|
2021-05-01 08:01:59 -07:00
|
|
|
expect(mockWSClient.implementation.send).toHaveBeenCalledWith(stringify({topic:"remote", payload:{brightness:255}}));
|
2020-09-04 09:42:24 -07:00
|
|
|
|
|
|
|
// Message
|
|
|
|
MQTT.publish.mockClear();
|
|
|
|
mockWSClient.implementation.send.mockClear();
|
2021-08-10 10:12:13 -07:00
|
|
|
mockWSClient.events.message(stringify({topic: 'bulb_color/set', payload: {state: 'ON'}}), false)
|
2020-09-04 09:42:24 -07:00
|
|
|
await flushPromises();
|
2021-05-01 08:01:59 -07:00
|
|
|
expect(MQTT.publish).toHaveBeenCalledTimes(1);
|
2020-09-04 09:42:24 -07:00
|
|
|
expect(MQTT.publish).toHaveBeenCalledWith(
|
|
|
|
'zigbee2mqtt/bulb_color',
|
2021-10-09 10:41:00 -07:00
|
|
|
stringify({state: 'ON', linkquality: null, update_available: null}),
|
2020-09-04 09:42:24 -07:00
|
|
|
{ retain: false, qos: 0 },
|
|
|
|
expect.any(Function)
|
|
|
|
);
|
2021-08-10 10:12:13 -07:00
|
|
|
mockWSClient.events.message(undefined, false);
|
|
|
|
mockWSClient.events.message("", false);
|
|
|
|
mockWSClient.events.message(null, false);
|
2020-09-13 06:38:10 -07:00
|
|
|
await flushPromises();
|
2020-09-04 09:42:24 -07:00
|
|
|
|
|
|
|
// Received message on socket
|
2021-05-01 08:01:59 -07:00
|
|
|
expect(mockWSClient.implementation.send).toHaveBeenCalledTimes(1);
|
2021-10-09 10:41:00 -07:00
|
|
|
expect(mockWSClient.implementation.send).toHaveBeenCalledWith(stringify({topic: 'bulb_color', payload: {state: 'ON', linkquality: null, update_available: null}}));
|
2020-09-04 09:42:24 -07:00
|
|
|
|
|
|
|
// Shouldnt set when not ready
|
|
|
|
mockWSClient.implementation.send.mockClear();
|
|
|
|
mockWSClient.implementation.readyState = 'close';
|
2021-08-10 10:12:13 -07:00
|
|
|
mockWSClient.events.message(stringify({topic: 'bulb_color/set', payload: {state: 'ON'}}), false)
|
2020-09-04 09:42:24 -07:00
|
|
|
expect(mockWSClient.implementation.send).toHaveBeenCalledTimes(0);
|
2020-10-07 08:05:46 -07:00
|
|
|
|
|
|
|
// Send last seen on connect
|
|
|
|
mockWSClient.implementation.send.mockClear();
|
|
|
|
mockWSClient.implementation.readyState = 'open';
|
|
|
|
settings.set(['advanced'], {last_seen: 'ISO_8601'});
|
|
|
|
mockWS.implementation.clients.push(mockWSClient.implementation);
|
|
|
|
await mockWS.events.connection(mockWSClient.implementation);
|
2021-05-01 08:01:59 -07:00
|
|
|
expect(mockWSClient.implementation.send).toHaveBeenCalledWith(stringify({topic:"remote", payload:{brightness:255, last_seen: "1970-01-01T00:00:01.000Z"}}));
|
2020-09-04 09:42:24 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
it('onReques/onUpgrade', async () => {
|
2021-02-06 08:32:20 -07:00
|
|
|
controller = new Controller(jest.fn(), jest.fn());
|
2020-09-04 09:42:24 -07:00
|
|
|
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"});
|
|
|
|
|
|
|
|
mockHTTP.variables.onRequest(1, 2);
|
2020-09-24 09:06:43 -07:00
|
|
|
expect(mockNodeStatic.implementation).toHaveBeenCalledTimes(1);
|
|
|
|
expect(mockNodeStatic.implementation).toHaveBeenCalledWith(1, 2, expect.any(Function));
|
2020-09-04 09:42:24 -07:00
|
|
|
});
|
|
|
|
|
2020-11-25 08:28:02 -07:00
|
|
|
it('Static server', async () => {
|
2021-02-06 08:32:20 -07:00
|
|
|
controller = new Controller(jest.fn(), jest.fn());
|
2020-09-04 09:42:24 -07:00
|
|
|
await controller.start();
|
|
|
|
|
2020-11-25 08:28:02 -07:00
|
|
|
expect(mockHTTP.implementation.listen).toHaveBeenCalledWith(8081, "127.0.0.1");
|
|
|
|
});
|
|
|
|
|
|
|
|
it('Authentification', async () => {
|
|
|
|
const authToken = 'sample-secure-token'
|
|
|
|
settings.set(['frontend'], {auth_token: authToken});
|
2021-02-06 08:32:20 -07:00
|
|
|
controller = new Controller(jest.fn(), jest.fn());
|
2020-11-25 08:28:02 -07:00
|
|
|
await controller.start();
|
2020-09-04 09:42:24 -07:00
|
|
|
|
|
|
|
const mockSocket = {destroy: jest.fn()};
|
2020-11-25 08:28:02 -07:00
|
|
|
mockWS.implementation.handleUpgrade.mockClear();
|
|
|
|
mockHTTP.events.upgrade({url: '/api'}, mockSocket, mockWSocket);
|
|
|
|
expect(mockWS.implementation.handleUpgrade).toHaveBeenCalledTimes(1);
|
2020-09-04 09:42:24 -07:00
|
|
|
expect(mockSocket.destroy).toHaveBeenCalledTimes(0);
|
2020-11-25 08:28:02 -07:00
|
|
|
expect(mockWS.implementation.handleUpgrade).toHaveBeenCalledWith({"url": "/api"}, mockSocket, mockWSocket, expect.any(Function));
|
|
|
|
expect(mockWSocket.close).toHaveBeenCalledWith(4401, "Unauthorized");
|
|
|
|
|
|
|
|
mockWSocket.close.mockClear();
|
|
|
|
mockWS.implementation.emit.mockClear();
|
|
|
|
|
|
|
|
const url = `/api?token=${authToken}`;
|
|
|
|
mockWS.implementation.handleUpgrade.mockClear();
|
|
|
|
mockHTTP.events.upgrade({url: url}, mockSocket, 3);
|
|
|
|
expect(mockWS.implementation.handleUpgrade).toHaveBeenCalledTimes(1);
|
|
|
|
expect(mockSocket.destroy).toHaveBeenCalledTimes(0);
|
|
|
|
expect(mockWS.implementation.handleUpgrade).toHaveBeenCalledWith({url}, mockSocket, 3, expect.any(Function));
|
|
|
|
expect(mockWSocket.close).toHaveBeenCalledTimes(0);
|
|
|
|
mockWS.implementation.handleUpgrade.mock.calls[0][3](mockWSocket);
|
|
|
|
expect(mockWS.implementation.emit).toHaveBeenCalledWith('connection', mockWSocket, {url});
|
|
|
|
|
2020-09-04 09:42:24 -07:00
|
|
|
});
|
|
|
|
});
|