mesh: MESH_NOTIFY_CHANNEL as comma-separated list + ch0 DM-only notify gate; v0.17.0
CI / build + unit (pull_request) Successful in 1m8s

This commit is contained in:
2026-09-25 16:18:07 +00:00
parent ff57f3eae1
commit 4ae932befe
8 changed files with 147 additions and 15 deletions
+106
View File
@@ -1046,6 +1046,112 @@ describe('MeshService opencode notifier', () => {
bridge.close();
}
});
it('notifies for rx texts on any channel in a comma-separated MESH_NOTIFY_CHANNEL list, and not for others', async () => {
const bridge = await startBridge();
process.env.MESH_HOST = '127.0.0.1';
process.env.MESH_PORT = String(bridge.port);
process.env.MESH_NOTIFY_CHANNEL = '2,5';
const svc = makeSvc();
try {
svc.onModuleInit();
await waitFor(
() => bridge.sockets.length === 1 && svc.status().connected,
);
// channel 5 is in the list — notified
bridge.sockets[0].write(textPacketFrame(KAMIL_NUM, 42, 'on-list', 5));
await waitFor(() => notifyMock.mock.calls.length === 1);
expect(notifyMock).toHaveBeenCalledWith(1, 'on-list', KAMIL_NUM);
// channel 3 is not in the list — captured in history but no notification
bridge.sockets[0].write(textPacketFrame(KAMIL_NUM, 42, 'off-list', 3));
await waitFor(() => svc.messages().length === 2);
expect(notifyMock).toHaveBeenCalledTimes(1);
} finally {
delete process.env.MESH_NOTIFY_CHANNEL;
svc.onModuleDestroy();
bridge.close();
}
});
it('ignores ch0 broadcasts even when 0 is in the list, but notifies for directed DMs', async () => {
const bridge = await startBridge();
process.env.MESH_HOST = '127.0.0.1';
process.env.MESH_PORT = String(bridge.port);
process.env.MESH_NOTIFY_CHANNEL = '3,0';
const svc = makeSvc();
try {
svc.onModuleInit();
await waitFor(
() => bridge.sockets.length === 1 && svc.status().connected,
);
// ch0 broadcast: shared default channel noise — no notification
bridge.sockets[0].write(
textPacketFrame(KAMIL_NUM, BROADCAST_ADDR, 'hello all', 0),
);
await waitFor(() => svc.messages().length === 1);
expect(notifyMock).not.toHaveBeenCalled();
// ch0 directed DM: both sender and recipient present — notified
bridge.sockets[0].write(textPacketFrame(KAMIL_NUM, 42, 'dm', 0));
await waitFor(() => notifyMock.mock.calls.length === 1);
expect(notifyMock).toHaveBeenCalledWith(1, 'dm', KAMIL_NUM);
} finally {
delete process.env.MESH_NOTIFY_CHANNEL;
svc.onModuleDestroy();
bridge.close();
}
});
it('notifies for a ch0 injection when 0 is in the list (multi-list)', () => {
process.env.MESH_NOTIFY_CHANNEL = '3,0';
const svc = makeSvc();
try {
const r = svc.injectMessage('hi', 42);
// injected records are exempt from the ch0 DM-only rule
expect(notifyMock).toHaveBeenCalledTimes(1);
expect(notifyMock).toHaveBeenCalledWith(r.id, 'hi', 42);
} finally {
delete process.env.MESH_NOTIFY_CHANNEL;
}
});
it('ignores empty and invalid entries in MESH_NOTIFY_CHANNEL', async () => {
const bridge = await startBridge();
process.env.MESH_HOST = '127.0.0.1';
process.env.MESH_PORT = String(bridge.port);
process.env.MESH_NOTIFY_CHANNEL = 'abc, ,5,-2,x7';
const svc = makeSvc();
try {
svc.onModuleInit();
await waitFor(
() => bridge.sockets.length === 1 && svc.status().connected,
);
// only the valid entry (5) survives parsing — notified
bridge.sockets[0].write(textPacketFrame(KAMIL_NUM, 42, 'ping', 5));
await waitFor(() => notifyMock.mock.calls.length === 1);
expect(notifyMock).toHaveBeenCalledWith(1, 'ping', KAMIL_NUM);
// channel 7 only appears inside the invalid entry "x7" — must not match
bridge.sockets[0].write(textPacketFrame(KAMIL_NUM, 42, 'nope', 7));
await waitFor(() => svc.messages().length === 2);
expect(notifyMock).toHaveBeenCalledTimes(1);
} finally {
delete process.env.MESH_NOTIFY_CHANNEL;
svc.onModuleDestroy();
bridge.close();
}
});
});
describe('MeshService forwarded ring', () => {