Skip to content

Commit

Permalink
Trial
Browse files Browse the repository at this point in the history
  • Loading branch information
NorthernMan54 committed Dec 3, 2024
1 parent 9f38fc8 commit eac10a3
Show file tree
Hide file tree
Showing 10 changed files with 584 additions and 531 deletions.
36 changes: 34 additions & 2 deletions src/lib/alexaActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ module.exports = {
alexaChannelController: alexaChannelController,
alexaInputController: alexaInputController,
alexaRangeController: alexaRangeController,
alexaModeController: alexaModeController
alexaModeController: alexaModeController,
destroy: destroy
};

function hapDiscovery(options) {
Expand All @@ -48,6 +49,10 @@ function hapDiscovery(options) {
// debug("Event Relay - 1", homebridge);
}

function destroy() {
homebridge.destroy();
}

function registerEvents(message) {
// debug("registerEvents", message);

Expand Down Expand Up @@ -932,7 +937,7 @@ function alexaMessage(message, callback) {
// For performance HAP GET Characteristices supports getting multiple in one call
// debug("alexaMessage - statusArray", statusArray);

processStatusArray(statusArray, message).then(response => {
processStatusArray.call(this, statusArray, message).then(response => {
debug("alexaMessage: Response", JSON.stringify(response,
null, 2));
callback(null, response);
Expand Down Expand Up @@ -984,6 +989,9 @@ async function processStatusArray(statusArray, message) {

return (alexaMessages.alexaStateResponse(resultArray, message));
} catch (err) {
if(this.deviceCleanup) {
reportDeviceError(message);
}
return (alexaMessages.alexaStateResponse(err, message));
}
}
Expand Down Expand Up @@ -1046,6 +1054,30 @@ function alexaEvent(events) {
});
}

function reportDeviceError(message) {
alexaLocal.alexaEvent({
"event": {
"header": {
"namespace": "Alexa.Discovery",
"name": "DeleteReport",
"messageId": messages.createMessageId(),
"payloadVersion": "3"
},
"payload": {
"endpoints": [
{
"endpointId": message.endpoint.endpointId,
}
],
"scope": {
"type": "BearerToken",
"token": "OAuth2.0 bearer token"
}
}
}
});
};

/*
Utility functions
Expand Down
95 changes: 95 additions & 0 deletions src/lib/alexaActions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
const { processStatusArray } = require('/Users/sgracey/Code/homebridge-alexa/src/lib/alexaActions.js');
const alexaMessages = require('/Users/sgracey/Code/homebridge-alexa/src/lib/alexaMessages.js');
const homebridge = require('hap-node-client').HAPNodeJSClient;

jest.mock('hap-node-client');
jest.mock('/Users/sgracey/Code/homebridge-alexa/src/lib/alexaMessages.js');

describe('processStatusArray', () => {
let statusArray;
let message;

beforeEach(() => {
statusArray = [
{
deviceID: 'device1',
body: '?id=1.1',
interface: 'Alexa.PowerController',
spacer: ',',
elements: [
{ interface: 'Alexa.PowerController', aid: 1, iid: 1 }
]
},
{
deviceID: 'device2',
body: '?id=2.1',
interface: 'Alexa.BrightnessController',
spacer: ',',
elements: [
{ interface: 'Alexa.BrightnessController', aid: 2, iid: 1 }
]
}
];
message = { directive: { header: { messageId: '123' } } };

homebridge.HAPstatusByDeviceID.mockImplementation((deviceID, body, callback) => {
if (deviceID === 'device1') {
callback(null, { characteristics: [{ aid: 1, iid: 1, value: true }] });
} else if (deviceID === 'device2') {
callback(null, { characteristics: [{ aid: 2, iid: 1, value: 50 }] });
} else {
callback(new Error('Device not found'));
}
});

alexaMessages.alexaStateResponse.mockImplementation((resultArray, message) => {
return { event: { header: { messageId: message.directive.header.messageId }, payload: { properties: resultArray } } };
});
});

test.skip('should process status array successfully', async () => {
const result = await processStatusArray(statusArray, message);
expect(result).toEqual({
event: {
header: { messageId: '123' },
payload: {
properties: [
{ interface: 'Alexa.PowerController', aid: 1, iid: 1, value: true },
{ interface: 'Alexa.BrightnessController', aid: 2, iid: 1, value: 50 }
]
}
}
});
});

test.skip('should handle errors during processing', async () => {
homebridge.HAPstatusByDeviceID.mockImplementationOnce((deviceID, body, callback) => {
callback(new Error('Device not found'));
});

const result = await processStatusArray(statusArray, message);
expect(result).toEqual({
event: {
header: { messageId: '123' },
payload: {
properties: [
{ interface: 'Alexa.PowerController', aid: 1, iid: 1, value: true },
{ interface: 'Alexa.BrightnessController', aid: 2, iid: 1, value: 50 }
]
}
}
});
});

test.skip('should return correct response format', async () => {
const result = await processStatusArray(statusArray, message);
expect(result).toHaveProperty('event.header.messageId', '123');
expect(result).toHaveProperty('event.payload.properties');
expect(result.event.payload.properties).toHaveLength(2);
});

afterAll(() => {
jest.clearAllMocks();
homebridge.destroy();
});
});
160 changes: 160 additions & 0 deletions src/lib/alexaLocal.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
const mqtt = require('mqtt');
const debug = require('debug');
const Bottleneck = require('bottleneck');
const { alexaLocal, alexaEvent, alexaPriorityEvent } = require('/Users/sgracey/Code/homebridge-alexa/src/lib/alexaLocal');

jest.mock('mqtt');
jest.mock('debug');
jest.mock('bottleneck');

describe.skip('alexaLocal', () => {
let options;
let mockClient;
let mockLimiter;

beforeEach(() => {
options = {
mqttURL: 'mqtt://test.mosquitto.org',
mqttOptions: { username: 'testUser' },
alexaService: {
setCharacteristic: jest.fn()
},
Characteristic: {
ContactSensorState: {
CONTACT_DETECTED: 'CONTACT_DETECTED',
CONTACT_NOT_DETECTED: 'CONTACT_NOT_DETECTED'
}
},
eventBus: {
listenerCount: jest.fn().mockReturnValue(1),
emit: jest.fn()
},
log: jest.fn()
};

mockClient = {
on: jest.fn(),
publish: jest.fn(),
subscribe: jest.fn(),
removeAllListeners: jest.fn(),
end: jest.fn()
};

mqtt.connect.mockReturnValue(mockClient);

mockLimiter = {
submit: jest.fn(),
on: jest.fn()
};

Bottleneck.mockImplementation(() => mockLimiter);
});

test('should connect to MQTT broker and set up event handlers', () => {
alexaLocal(options);

expect(mqtt.connect).toHaveBeenCalledWith(options.mqttURL, options.mqttOptions);
expect(mockClient.on).toHaveBeenCalledWith('connect', expect.any(Function));
expect(mockClient.on).toHaveBeenCalledWith('offline', expect.any(Function));
expect(mockClient.on).toHaveBeenCalledWith('reconnect', expect.any(Function));
expect(mockClient.on).toHaveBeenCalledWith('error', expect.any(Function));
});

test('should handle successful connection', () => {
alexaLocal(options);

const connectHandler = mockClient.on.mock.calls[0][1];
connectHandler();

expect(mockClient.subscribe).toHaveBeenCalledWith('command/testUser/#');
expect(mockClient.publish).toHaveBeenCalledWith('presence/testUser/1', expect.any(String));
expect(options.alexaService.setCharacteristic).toHaveBeenCalledWith(
options.Characteristic.ContactSensorState,
options.Characteristic.ContactSensorState.CONTACT_DETECTED
);
});

test('should handle offline event', () => {
alexaLocal(options);

const offlineHandler = mockClient.on.mock.calls[1][1];
offlineHandler();

expect(options.alexaService.setCharacteristic).toHaveBeenCalledWith(
options.Characteristic.ContactSensorState,
options.Characteristic.ContactSensorState.CONTACT_NOT_DETECTED
);
});

test('should handle reconnect event', () => {
alexaLocal(options);

const reconnectHandler = mockClient.on.mock.calls[2][1];
reconnectHandler();

expect(options.alexaService.setCharacteristic).toHaveBeenCalledWith(
options.Characteristic.ContactSensorState,
options.Characteristic.ContactSensorState.CONTACT_NOT_DETECTED
);
});

test('should handle error event', () => {
alexaLocal(options);

const errorHandler = mockClient.on.mock.calls[3][1];
const error = new Error('Test error');
error.code = 5;
errorHandler(error);

expect(options.alexaService.setCharacteristic).toHaveBeenCalledWith(
options.Characteristic.ContactSensorState,
options.Characteristic.ContactSensorState.CONTACT_NOT_DETECTED
);
expect(options.log.error).toHaveBeenCalledWith(expect.stringContaining('Login to homebridge.ca failed'));
});

test('should publish alexaEvent', () => {
alexaLocal(options);
const message = { test: 'message' };

alexaEvent(message);

expect(mockLimiter.submit).toHaveBeenCalledWith(expect.any(Function));
});

test('should publish alexaPriorityEvent', () => {
alexaLocal(options);
const message = { test: 'priorityMessage' };

alexaPriorityEvent(message);

expect(mockLimiter.submit).toHaveBeenCalledWith({ priority: 4 }, expect.any(Function));
});

test('should generate alexaErrorResponse', () => {
const message = {
directive: {
header: {
messageId: 'testMessageId'
}
}
};

const response = _alexaErrorResponse(message);

expect(response).toEqual({
event: {
header: {
name: 'ErrorResponse',
namespace: 'Alexa',
payloadVersion: '3',
messageId: 'testMessageId'
},
payload: {
type: 'INVALID_DIRECTIVE',
message: 'No listener for directive'
}
}
});
});
});
Loading

0 comments on commit eac10a3

Please sign in to comment.