feat(filter): add isExcluded helper with unit tests
Glob-based entity_id matcher used by the upcoming exclude-filter feature. Single wildcard `*` (matches any sequence including dots), all other characters matched literally with regex metacharacters escaped. Case-sensitive, anchored. Empty pattern array always returns false, so the helper is a no-op when the adapter is not configured to filter anything. New mocha unit-test suite invoked via `npm run test:unit` (separate from the existing js-controller-backed test:integration).
This commit is contained in:
@@ -50,6 +50,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"test:integration": "mocha --exit",
|
"test:integration": "mocha --exit",
|
||||||
"test:package": "mocha test/testPackageFiles.js --exit",
|
"test:package": "mocha test/testPackageFiles.js --exit",
|
||||||
|
"test:unit": "mocha test/testEntityFilter.js --exit",
|
||||||
"test": "npm run test:integration",
|
"test": "npm run test:integration",
|
||||||
"build:tsc": "tsc -p tsconfig.build.json",
|
"build:tsc": "tsc -p tsconfig.build.json",
|
||||||
"build": "npm run build:tsc",
|
"build": "npm run build:tsc",
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Returns true if entityId matches any of the supplied glob patterns.
|
||||||
|
* Glob syntax: `*` is the only wildcard and matches any sequence of characters
|
||||||
|
* (including dots). All other characters are matched literally (regex
|
||||||
|
* metacharacters are escaped). Matching is case-sensitive and anchored to
|
||||||
|
* the full entity_id.
|
||||||
|
*
|
||||||
|
* An empty patterns array always returns false.
|
||||||
|
*/
|
||||||
|
export function isExcluded(entityId: string, patterns: string[]): boolean {
|
||||||
|
if (!patterns || patterns.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (const pattern of patterns) {
|
||||||
|
if (globToRegex(pattern).test(entityId)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function globToRegex(pattern: string): RegExp {
|
||||||
|
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*');
|
||||||
|
return new RegExp(`^${escaped}$`);
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
const { expect } = require('chai');
|
||||||
|
const { isExcluded } = require('../build/lib/entityFilter');
|
||||||
|
|
||||||
|
describe('entityFilter.isExcluded', () => {
|
||||||
|
it('returns false for an empty pattern list', () => {
|
||||||
|
expect(isExcluded('switch.iob_anything', [])).to.equal(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('matches a simple glob with leading wildcard', () => {
|
||||||
|
expect(isExcluded('switch.iob_shelly_xyz', ['*.iob_*'])).to.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('matches the bridge mirror naming pattern', () => {
|
||||||
|
expect(isExcluded('light.iob_ha_eg_wz1__e_licht_decke', ['*.iob_*__*'])).to.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles entities that miss a required literal segment', () => {
|
||||||
|
// *.iob_*__* requires the literal `__` segment — entities without it do not match
|
||||||
|
expect(isExcluded('switch.iob_', ['*.iob_*__*'])).to.equal(false);
|
||||||
|
expect(isExcluded('switch.iob_foo', ['*.iob_*__*'])).to.equal(false);
|
||||||
|
// Naming-Convention safety: similarly named entities without `__` are safe
|
||||||
|
expect(isExcluded('sensor.scheune_temperatur', ['*.sc_*__*'])).to.equal(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('matches multiple patterns (OR semantic)', () => {
|
||||||
|
const patterns = ['*.iob_*__*', '*.knx_*', '*.ha_*__*'];
|
||||||
|
expect(isExcluded('switch.knx_foo', patterns)).to.equal(true);
|
||||||
|
expect(isExcluded('switch.ha_eg_wz1__e_licht_decke', patterns)).to.equal(true);
|
||||||
|
expect(isExcluded('switch.something_else', patterns)).to.equal(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is case sensitive', () => {
|
||||||
|
expect(isExcluded('switch.IOB_foo', ['*.iob_*'])).to.equal(false);
|
||||||
|
expect(isExcluded('switch.iob_foo', ['*.IOB_*'])).to.equal(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('treats * as matching any characters including dots', () => {
|
||||||
|
// We use full entity_id including the leading domain, so this is fine.
|
||||||
|
expect(isExcluded('switch.iob_foo', ['*foo*'])).to.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('escapes regex metacharacters in patterns', () => {
|
||||||
|
// `.` in pattern matches literal `.`, not "any char"
|
||||||
|
expect(isExcluded('switch.iob_foo', ['switch.iob_foo'])).to.equal(true);
|
||||||
|
expect(isExcluded('switchXiob_foo', ['switch.iob_foo'])).to.equal(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user