Skip to content

Commit

Permalink
Release 1.2.0
Browse files Browse the repository at this point in the history
  • Loading branch information
Luligu committed Dec 2, 2024
1 parent 7444b0a commit 8dd278c
Show file tree
Hide file tree
Showing 8 changed files with 199 additions and 44 deletions.
6 changes: 2 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,5 @@ coverage/
hs_err_pid*
replay_pid*

# zigbee2mqtt
bridge-info.json
bridge-devices.json
bridge-groups.json
temp
src/mock
3 changes: 2 additions & 1 deletion .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -165,4 +165,5 @@ screenshot
TODO.md
yellow-button.png
create-release.js
tsconfig.*
tsconfig.*
temp
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ If you like this project and find it useful, please consider giving it a star on

All notable changes to this project will be documented in this file.

## [1.2.0] - 2024-11-30
## [1.2.0] - 2024-12-02

### Added

Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"test:verbose": "node --experimental-vm-modules node_modules/jest/bin/jest.js --verbose",
"test:watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch",
"test:coverage": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage",
"test:platform": "node --experimental-vm-modules node_modules/jest/bin/jest.js src/platform.test.ts --verbose --coverage",
"lint": "eslint --max-warnings=0",
"lint:fix": "eslint --max-warnings=0 --fix",
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,css,md}\"",
Expand Down Expand Up @@ -92,4 +93,4 @@
"typescript": "5.7.2",
"typescript-eslint": "8.16.0"
}
}
}
13 changes: 4 additions & 9 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,20 @@ describe('initializePlugin', () => {
} as unknown as Matterbridge;
mockLog = { error: jest.fn(), warn: jest.fn(), info: jest.fn(), debug: jest.fn() } as unknown as AnsiLogger;
mockConfig = {
'name': 'matterbridge-test',
'name': 'matterbridge-somfy-tahoma',
'type': 'DynamicPlatform',
'username': 'None',
'password': 'None',
'service': 'somfy_europe',
'blackList': [],
'whiteList': [],
'debug': false,
'unregisterOnShutdown': false,
} as PlatformConfig;
});

it('should return an instance of TestPlatform', () => {
it('should return an instance of SomfyTahomaPlatform', () => {
const result = initializePlugin(mockMatterbridge, mockLog, mockConfig);

expect(result).toBeInstanceOf(SomfyTahomaPlatform);
});

it('should shutdown the instance of TestPlatform', () => {
const result = initializePlugin(mockMatterbridge, mockLog, mockConfig);

expect(result).toBeInstanceOf(SomfyTahomaPlatform);
});
});
182 changes: 168 additions & 14 deletions src/platform.test.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,105 @@
import { Matterbridge, PlatformConfig } from 'matterbridge';
import { AnsiLogger } from 'matterbridge/logger';
/* eslint-disable no-console */
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Matterbridge, MatterbridgeDevice, MatterbridgeEndpoint, PlatformConfig } from 'matterbridge';
import { AnsiLogger, dn, LogLevel, wr } from 'matterbridge/logger';
import { SomfyTahomaPlatform } from './platform';

import { jest } from '@jest/globals';
import { Client, Device } from 'overkiz-client';

describe('TestPlatform', () => {
let mockMatterbridge: Matterbridge;
let mockLog: AnsiLogger;
let mockConfig: PlatformConfig;
let testPlatform: SomfyTahomaPlatform;
let somfyPlatform: SomfyTahomaPlatform;

// const log = new AnsiLogger({ logName: 'shellyDeviceTest', logTimestampFormat: TimestampFormat.TIME_MILLIS, logDebug: true });
let clientConnectSpy: jest.SpiedFunction<(user: string, password: string) => Promise<void>>;
let clientGetDevicesSpy: jest.SpiedFunction<(user: string, password: string) => Promise<Device[]>>;
let clientExecuteSpy: jest.SpiedFunction<(oid: any, execution: any) => Promise<any>>;

beforeAll(() => {
// Spy on the Client.connect method
clientConnectSpy = jest.spyOn(Client.prototype, 'connect').mockImplementation((user: string, password: string) => {
console.error(`Mocked Client.connect(${user}, ${password})`);
return Promise.resolve();
});
clientGetDevicesSpy = jest.spyOn(Client.prototype, 'getDevices').mockImplementation(() => {
console.error(`Mocked Client.getDevices()`);
return Promise.resolve([]);
});
clientExecuteSpy = jest.spyOn(Client.prototype, 'execute').mockImplementation((oid: any, execution: any) => {
console.error(`Mocked Client.execute(${oid}, ${execution})`);
return Promise.resolve();
});

mockMatterbridge = {
addBridgedDevice: jest.fn(),
matterbridgeDirectory: '',
matterbridgePluginDirectory: 'temp',
systemInformation: { ipv4Address: undefined },
matterbridgeVersion: '1.6.5',
removeAllBridgedDevices: jest.fn(),
addBridgedDevice: jest.fn(async (pluginName: string, device: MatterbridgeDevice) => {
// console.error('addBridgedDevice called');
}),
addBridgedEndpoint: jest.fn(async (pluginName: string, device: MatterbridgeEndpoint) => {
device.number = 100;
// console.error('addBridgedEndpoint called');
}),
removeBridgedDevice: jest.fn(async (pluginName: string, device: MatterbridgeDevice) => {
// console.error('removeBridgedDevice called');
}),
removeBridgedEndpoint: jest.fn(async (pluginName: string, device: MatterbridgeEndpoint) => {
// console.error('removeBridgedEndpoint called');
}),
removeAllBridgedDevices: jest.fn(async (pluginName: string) => {
// console.error('removeAllBridgedDevices called');
}),
removeAllBridgedEndpoints: jest.fn(async (pluginName: string) => {
// console.error('removeAllBridgedEndpoints called');
}),
} as unknown as Matterbridge;
mockLog = { error: jest.fn(), warn: jest.fn(), info: jest.fn(), debug: jest.fn() } as unknown as AnsiLogger;
mockLog = {
fatal: jest.fn((message: string, ...parameters: any[]) => {
console.error('mockLog.fatal', message, ...parameters);
}),
error: jest.fn((message: string, ...parameters: any[]) => {
console.error('mockLog.error', message, ...parameters);
}),
warn: jest.fn((message: string, ...parameters: any[]) => {
console.error('mockLog.warn', message, ...parameters);
}),
notice: jest.fn((message: string, ...parameters: any[]) => {
console.error('mockLog.notice', message, ...parameters);
}),
info: jest.fn((message: string, ...parameters: any[]) => {
console.error('mockLog.info', message, ...parameters);
}),
debug: jest.fn((message: string, ...parameters: any[]) => {
console.error('mockLog.debug', message, ...parameters);
}),
} as unknown as AnsiLogger;
mockConfig = {
'name': 'matterbridge-test',
'name': 'matterbridge-somfy-tahoma',
'type': 'DynamicPlatform',
'username': 'None',
'password': 'None',
'service': 'somfy_europe',
'blackList': [],
'whiteList': [],
'debug': false,
'unregisterOnShutdown': false,
} as PlatformConfig;
});

// testPlatform = new SomfyTahomaPlatform(mockMatterbridge, mockLog, mockConfig);
beforeEach(() => {
jest.clearAllMocks();
});

it('should not initialize platform without username and password', () => {
mockConfig.username = undefined;
mockConfig.password = undefined;
mockConfig.service = undefined;
testPlatform = new SomfyTahomaPlatform(mockMatterbridge, mockLog, mockConfig);
somfyPlatform = new SomfyTahomaPlatform(mockMatterbridge, mockLog, mockConfig);
expect(mockLog.info).toHaveBeenCalledWith('Initializing platform:', mockConfig.name);
expect(mockLog.error).toHaveBeenCalledWith('No service or username or password provided for:', mockConfig.name);
});
Expand All @@ -47,23 +108,116 @@ describe('TestPlatform', () => {
mockConfig.username = 'None';
mockConfig.password = 'None';
mockConfig.service = 'somfy_europe';
testPlatform = new SomfyTahomaPlatform(mockMatterbridge, mockLog, mockConfig);
somfyPlatform = new SomfyTahomaPlatform(mockMatterbridge, mockLog, mockConfig);
expect(mockLog.info).toHaveBeenCalledWith('Initializing platform:', mockConfig.name);
expect(mockLog.info).toHaveBeenCalledWith('Finished initializing platform:', mockConfig.name);
expect(mockLog.info).toHaveBeenCalledWith('Starting client Tahoma service somfy_europe with user None password: None');
});

it('should return false and log a warning if entity is not in the whitelist', () => {
(somfyPlatform as any).whiteList = ['entity1', 'entity2'];
(somfyPlatform as any).blackList = [];

const result = (somfyPlatform as any).validateWhiteBlackList('entity3');

expect(result).toBe(false);
expect(mockLog.warn).toHaveBeenCalledWith(`Skipping ${dn}entity3${wr} because not in whitelist`);
});

it('should return false and log a warning if entity is in the blacklist', () => {
(somfyPlatform as any).whiteList = [];
(somfyPlatform as any).blackList = ['entity3'];

const result = (somfyPlatform as any).validateWhiteBlackList('entity3');

expect(result).toBe(false);
expect(mockLog.warn).toHaveBeenCalledWith(`Skipping ${dn}entity3${wr} because in blacklist`);
});

it('should return true if entity is in the whitelist', () => {
(somfyPlatform as any).whiteList = ['entity3'];
(somfyPlatform as any).blackList = [];

const result = (somfyPlatform as any).validateWhiteBlackList('entity3');

expect(result).toBe(true);
expect(mockLog.warn).not.toHaveBeenCalled();
});

it('should return true if entity is not in the blacklist and whitelist is empty', () => {
(somfyPlatform as any).whiteList = [];
(somfyPlatform as any).blackList = [];

const result = (somfyPlatform as any).validateWhiteBlackList('entity3');

expect(result).toBe(true);
expect(mockLog.warn).not.toHaveBeenCalled();
});

it('should return true if both whitelist and blacklist are empty', () => {
(somfyPlatform as any).whiteList = [];
(somfyPlatform as any).blackList = [];

const result = (somfyPlatform as any).validateWhiteBlackList('entity3');

expect(result).toBe(true);
expect(mockLog.warn).not.toHaveBeenCalled();
});

it('should validate version', () => {
mockMatterbridge.matterbridgeVersion = '1.5.4';
expect(somfyPlatform.verifyMatterbridgeVersion('1.5.3')).toBe(true);
expect(somfyPlatform.verifyMatterbridgeVersion('1.5.4')).toBe(true);
expect(somfyPlatform.verifyMatterbridgeVersion('2.0.0')).toBe(false);
});

it('should validate version beta', () => {
mockMatterbridge.matterbridgeVersion = '1.5.4-dev.1';
expect(somfyPlatform.verifyMatterbridgeVersion('1.5.3')).toBe(true);
expect(somfyPlatform.verifyMatterbridgeVersion('1.5.4')).toBe(true);
expect(somfyPlatform.verifyMatterbridgeVersion('2.0.0')).toBe(false);
mockMatterbridge.matterbridgeVersion = '1.5.5';
});

it('should throw because of version', () => {
mockMatterbridge.matterbridgeVersion = '1.5.4';
expect(() => new SomfyTahomaPlatform(mockMatterbridge, mockLog, mockConfig)).toThrow();
mockMatterbridge.matterbridgeVersion = '1.6.5';
});

it('should call onStart with reason', async () => {
await testPlatform.onStart('Test reason');
await somfyPlatform.onStart('Test reason');
expect(mockLog.info).toHaveBeenCalledWith('onStart called with reason:', 'Test reason');
expect(clientConnectSpy).toHaveBeenCalledWith('None', 'None');
});

it('should call onStart with reason and log error', async () => {
const client = (somfyPlatform as any).tahomaClient;
(somfyPlatform as any).tahomaClient = undefined;
await somfyPlatform.onStart('Test reason');
expect(mockLog.error).toHaveBeenCalledWith('TaHoma service not created');
expect(clientConnectSpy).not.toHaveBeenCalledWith('None', 'None');
(somfyPlatform as any).tahomaClient = client;
});

it('should call onStart with reason and log error if connect throws', async () => {
clientConnectSpy.mockImplementationOnce(() => {
throw new Error('Error connecting to TaHoma service');
});
await somfyPlatform.onStart('Test reason');
expect(mockLog.info).toHaveBeenCalledWith('onStart called with reason:', 'Test reason');
expect(mockLog.error).not.toHaveBeenCalledWith('TaHoma service not created');
expect(mockLog.error).toHaveBeenCalledWith(expect.stringContaining('Error connecting to TaHoma service'), undefined);
expect(clientConnectSpy).toHaveBeenCalledWith('None', 'None');
});

it('should call onConfigure', async () => {
await testPlatform.onConfigure();
await somfyPlatform.onConfigure();
expect(mockLog.info).toHaveBeenCalledWith('onConfigure called');
});

it('should call onShutdown with reason', async () => {
await testPlatform.onShutdown('Test reason');
await somfyPlatform.onShutdown('Test reason');
expect(mockLog.info).toHaveBeenCalledWith('onShutdown called with reason:', 'Test reason');
});
});
22 changes: 17 additions & 5 deletions src/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ import {
MatterbridgeEndpoint,
coverDevice,
} from 'matterbridge';
import { AnsiLogger, BLUE, debugStringify, dn, rs, wr } from 'matterbridge/logger';
import { isValidNumber } from 'matterbridge/utils';
import { AnsiLogger, BLUE, debugStringify, dn, rs, wr, CYAN, db, ign, nf, YELLOW } from 'matterbridge/logger';
import { NodeStorageManager } from 'matterbridge/storage';
import { isValidNumber } from 'matterbridge/utils';

import { Action, Client, Command, Device, Execution } from 'overkiz-client';
import path from 'path';
import { CYAN, db, ign, nf, YELLOW } from 'node-ansi-logger';
import { promises as fs } from 'fs';

type MovementDuration = Record<string, number>;
const Stopped = WindowCovering.MovementStatus.Stopped;
Expand Down Expand Up @@ -114,7 +114,7 @@ export class SomfyTahomaPlatform extends MatterbridgeDynamicPlatform {
override async onStart(reason?: string) {
this.log.info('onStart called with reason:', reason ?? 'none');
if (!this.tahomaClient) {
this.log.error('TaHoma service not connected');
this.log.error('TaHoma service not created');
return;
}
try {
Expand Down Expand Up @@ -191,6 +191,19 @@ export class SomfyTahomaPlatform extends MatterbridgeDynamicPlatform {

this.log.info('TaHoma', devices.length, 'devices discovered');

// Create the plugin directory inside the Matterbridge plugin directory
await fs.mkdir(path.join(this.matterbridge.matterbridgePluginDirectory, 'matterbridge-somfy-tahoma'), { recursive: true });

// Write the discovered devices to a file
const fileName = path.join(this.matterbridge.matterbridgePluginDirectory, 'matterbridge-somfy-tahoma', 'devices.json');
fs.writeFile(fileName, JSON.stringify(devices, null, 2))
.then(() => {
this.log.debug(`Devices successfully written to ${fileName}`);
})
.catch((error) => {
this.log.error(`Error writing devices to ${fileName}:`, error);
});

for (const device of devices) {
this.log.debug(`Device: ${BLUE}${device.label}${rs}`);
this.log.debug(`- uniqueName ${device.uniqueName}`);
Expand All @@ -199,7 +212,6 @@ export class SomfyTahomaPlatform extends MatterbridgeDynamicPlatform {
this.log.debug(`- deviceURL ${device.deviceURL}`);
this.log.debug(`- commands ${debugStringify(device.commands)}`);
this.log.debug(`- states ${debugStringify(device.states)}`);
// this.log.debug(`Device: ${device.label} uniqueName ${device.uniqueName} uiClass ${device.definition.uiClass} deviceURL ${device.deviceURL} serial ${device.serialNumber}`);
const supportedUniqueNames = [
'Blind',
'BlindRTSComponent',
Expand Down
12 changes: 3 additions & 9 deletions tsconfig.production.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,6 @@
"sourceMap": false,
"removeComments": true
},
"include": [
"src/**/*.ts"
],
"exclude": [
"**/*.spec.ts",
"**/*.test.ts",
"**/__test__/*"
]
}
"include": ["src/**/*.ts"],
"exclude": ["**/*.spec.ts", "**/*.test.ts", "**/__test__/*"]
}

0 comments on commit 8dd278c

Please sign in to comment.