@djodjonx/x32-simulator 0.0.3 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +22 -0
  3. package/dist/server.cjs +4 -5
  4. package/dist/server.d.cts +1 -10
  5. package/dist/server.d.mts +1 -10
  6. package/dist/server.mjs +5 -3
  7. package/package.json +5 -1
  8. package/.commitlintrc.json +0 -3
  9. package/.github/workflows/publish.yml +0 -38
  10. package/.husky/commit-msg +0 -1
  11. package/.husky/pre-commit +0 -1
  12. package/.oxlintrc.json +0 -56
  13. package/INSTALL.md +0 -107
  14. package/docs/OSC-Communication.md +0 -184
  15. package/docs/X32-INTERNAL.md +0 -262
  16. package/docs/X32-OSC.pdf +0 -0
  17. package/docs/behringer-x32-x32-osc-remote-protocol-en-44463.pdf +0 -0
  18. package/src/application/use-cases/BroadcastUpdatesUseCase.ts +0 -120
  19. package/src/application/use-cases/ManageSessionsUseCase.ts +0 -9
  20. package/src/application/use-cases/ProcessPacketUseCase.ts +0 -26
  21. package/src/application/use-cases/SimulationService.ts +0 -146
  22. package/src/domain/entities/SubscriptionManager.ts +0 -126
  23. package/src/domain/entities/X32State.ts +0 -78
  24. package/src/domain/models/MeterConfig.ts +0 -22
  25. package/src/domain/models/MeterData.ts +0 -59
  26. package/src/domain/models/OscMessage.ts +0 -93
  27. package/src/domain/models/X32Address.ts +0 -72
  28. package/src/domain/models/X32Node.ts +0 -43
  29. package/src/domain/models/types.ts +0 -86
  30. package/src/domain/ports/ILogger.ts +0 -27
  31. package/src/domain/ports/INetworkGateway.ts +0 -8
  32. package/src/domain/ports/IStateRepository.ts +0 -16
  33. package/src/domain/services/MeterService.ts +0 -46
  34. package/src/domain/services/OscMessageHandler.ts +0 -88
  35. package/src/domain/services/SchemaFactory.ts +0 -308
  36. package/src/domain/services/SchemaRegistry.ts +0 -67
  37. package/src/domain/services/StaticResponseService.ts +0 -52
  38. package/src/domain/services/strategies/BatchStrategy.ts +0 -74
  39. package/src/domain/services/strategies/MeterStrategy.ts +0 -45
  40. package/src/domain/services/strategies/NodeDiscoveryStrategy.ts +0 -36
  41. package/src/domain/services/strategies/OscCommandStrategy.ts +0 -22
  42. package/src/domain/services/strategies/StateAccessStrategy.ts +0 -71
  43. package/src/domain/services/strategies/StaticResponseStrategy.ts +0 -42
  44. package/src/domain/services/strategies/SubscriptionStrategy.ts +0 -56
  45. package/src/infrastructure/mappers/OscCodec.ts +0 -54
  46. package/src/infrastructure/repositories/InMemoryStateRepository.ts +0 -37
  47. package/src/infrastructure/services/ConsoleLogger.ts +0 -177
  48. package/src/infrastructure/services/UdpNetworkGateway.ts +0 -100
  49. package/src/presentation/cli/server.ts +0 -194
  50. package/src/presentation/library/library.ts +0 -139
  51. package/tests/application/use-cases/BroadcastUpdatesUseCase.test.ts +0 -104
  52. package/tests/application/use-cases/ManageSessionsUseCase.test.ts +0 -12
  53. package/tests/application/use-cases/ProcessPacketUseCase.test.ts +0 -49
  54. package/tests/application/use-cases/SimulationService.test.ts +0 -77
  55. package/tests/domain/entities/SubscriptionManager.test.ts +0 -50
  56. package/tests/domain/entities/X32State.test.ts +0 -52
  57. package/tests/domain/models/MeterData.test.ts +0 -23
  58. package/tests/domain/models/OscMessage.test.ts +0 -38
  59. package/tests/domain/models/X32Address.test.ts +0 -30
  60. package/tests/domain/models/X32Node.test.ts +0 -30
  61. package/tests/domain/services/MeterService.test.ts +0 -27
  62. package/tests/domain/services/OscMessageHandler.test.ts +0 -51
  63. package/tests/domain/services/SchemaRegistry.test.ts +0 -47
  64. package/tests/domain/services/StaticResponseService.test.ts +0 -15
  65. package/tests/domain/services/strategies/BatchStrategy.test.ts +0 -41
  66. package/tests/domain/services/strategies/MeterStrategy.test.ts +0 -19
  67. package/tests/domain/services/strategies/NodeDiscoveryStrategy.test.ts +0 -22
  68. package/tests/domain/services/strategies/StateAccessStrategy.test.ts +0 -49
  69. package/tests/domain/services/strategies/StaticResponseStrategy.test.ts +0 -15
  70. package/tests/domain/services/strategies/SubscriptionStrategy.test.ts +0 -45
  71. package/tests/infrastructure/mappers/OscCodec.test.ts +0 -41
  72. package/tests/infrastructure/repositories/InMemoryStateRepository.test.ts +0 -29
  73. package/tests/infrastructure/services/ConsoleLogger.test.ts +0 -74
  74. package/tests/infrastructure/services/UdpNetworkGateway.test.ts +0 -61
  75. package/tests/presentation/cli/server.test.ts +0 -178
  76. package/tests/presentation/library/library.test.ts +0 -13
  77. package/tsconfig.json +0 -21
  78. package/tsdown.config.ts +0 -15
  79. package/vitest.config.ts +0 -9
@@ -1,194 +0,0 @@
1
- #!/usr/bin/env node
2
- import * as readline from 'node:readline';
3
- import * as fs from 'node:fs';
4
- import * as path from 'node:path';
5
- import { fileURLToPath } from 'node:url';
6
- import { SimulationService } from '../../application/use-cases/SimulationService';
7
- import { ConsoleLogger, LogLevel } from '../../infrastructure/services/ConsoleLogger';
8
- import { UdpNetworkGateway } from '../../infrastructure/services/UdpNetworkGateway';
9
- import { InMemoryStateRepository } from '../../infrastructure/repositories/InMemoryStateRepository';
10
- import { SchemaRegistry } from '../../domain/services/SchemaRegistry';
11
- import { SchemaFactory } from '../../domain/services/SchemaFactory';
12
- import { OscCodec } from '../../infrastructure/mappers/OscCodec';
13
-
14
- // Basic .env loader
15
- export const loadEnv = () => {
16
- try {
17
- const envPath = path.resolve(process.cwd(), '.env');
18
- if (fs.existsSync(envPath)) {
19
- const content = fs.readFileSync(envPath, 'utf8');
20
- content.split('\n').forEach(line => {
21
- const match = line.match(/^([^=]+)=(.*)$/);
22
- if (match) {
23
- const key = match[1].trim();
24
- const value = match[2].trim();
25
- if (!process.env[key]) {
26
- process.env[key] = value;
27
- }
28
- }
29
- });
30
- }
31
- } catch (err) {
32
- const message = err instanceof Error ? err.message : String(err);
33
- console.warn('Failed to load .env file', message);
34
- }
35
- };
36
-
37
- export const parseArgs = (argv: string[]) => {
38
- const args = argv.slice(2);
39
- let cliPort: number | undefined;
40
- let cliHost: string | undefined;
41
-
42
- for (let i = 0; i < args.length; i++) {
43
- const arg = args[i];
44
-
45
- // Handle flags
46
- if (arg === '--port' || arg === '-p') {
47
- const next = args[i + 1];
48
- if (next && /^\d+$/.test(next)) {
49
- cliPort = parseInt(next, 10);
50
- i++; // Skip next
51
- continue;
52
- }
53
- }
54
- if (arg === '--ip' || arg === '-h' || arg === '--host') {
55
- const next = args[i + 1];
56
- if (next) {
57
- cliHost = next;
58
- i++; // Skip next
59
- continue;
60
- }
61
- }
62
-
63
- // Handle positional arguments (simple heuristic)
64
- // If it looks like an IP (x.x.x.x), treat as Host
65
- if (!cliHost && /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(arg)) {
66
- cliHost = arg;
67
- continue;
68
- }
69
-
70
- // If it looks like a port number (and we haven't set port yet), treat as Port
71
- if (!cliPort && /^\d+$/.test(arg)) {
72
- cliPort = parseInt(arg, 10);
73
- continue;
74
- }
75
- }
76
-
77
- const PORT = cliPort || parseInt(process.env.X32_PORT || '10023', 10);
78
- const HOST = cliHost || process.env.X32_IP || '0.0.0.0';
79
-
80
- return { PORT, HOST };
81
- };
82
-
83
- export const bootstrap = async () => {
84
- loadEnv();
85
- const { PORT, HOST } = parseArgs(process.argv);
86
-
87
- // 0. Configure Logging
88
- const logger = ConsoleLogger.getInstance();
89
- logger.setLevel(LogLevel.DEBUG);
90
-
91
- // 1. Initialize Domain Services (Registry) & Mappers
92
- const schemaFactory = new SchemaFactory();
93
- const schemaRegistry = new SchemaRegistry(schemaFactory);
94
- const oscCodec = new OscCodec(schemaRegistry);
95
-
96
- // 2. Initialize Infrastructure
97
- const gateway = new UdpNetworkGateway(logger, oscCodec);
98
- const stateRepo = new InMemoryStateRepository(logger, schemaRegistry);
99
-
100
- // 3. Initialize Application Service
101
- const service = new SimulationService(
102
- gateway,
103
- logger,
104
- stateRepo,
105
- schemaRegistry,
106
- PORT,
107
- HOST
108
- );
109
-
110
- try {
111
- await service.start();
112
- console.log(`
113
- ╔════════════════════════════════════════╗
114
- ║ 🚀 X32 SIMULATION SERVER ACTIVE ║
115
- ╚════════════════════════════════════════╝
116
- • IP: ${HOST}
117
- • Port: ${PORT}
118
- • Protocol: UDP (Strict X32 Schema)
119
- • Logging: ENHANCED (DEBUG)
120
-
121
- 👉 Type "exit" or "stop" to shut down.
122
- 👉 Type "reset" to reset faders to default.
123
- `);
124
- } catch (err: any) {
125
- if (err.code === 'EADDRNOTAVAIL') {
126
- console.error(`\n\x1b[31m❌ FAILED TO BIND TO IP: ${HOST}\x1b[0m`);
127
- console.error(`\nThe IP address you requested does not exist on this machine.`);
128
- console.error(`You likely need to create a network alias (Loopback) for it.\n`);
129
-
130
- const isMac = process.platform === 'darwin';
131
- const isLinux = process.platform === 'linux';
132
- const isWin = process.platform === 'win32';
133
-
134
- if (isMac || isLinux || isWin) {
135
- console.error(`To fix this, try running the following command:\n`);
136
- if (isMac) {
137
- console.error(` \x1b[36msudo ifconfig lo0 alias ${HOST}\x1b[0m`);
138
- } else if (isLinux) {
139
- console.error(` \x1b[36msudo ip addr add ${HOST}/32 dev lo\x1b[0m`);
140
- } else if (isWin) {
141
- console.error(` \x1b[36mnetsh interface ip add address "Loopback" ${HOST} 255.255.255.255\x1b[0m`);
142
- console.error(` (Note: Requires 'Microsoft Loopback Adapter' installed and named "Loopback")`);
143
- }
144
- }
145
- console.error(`\nAlternatively, remove the IP argument to listen on all interfaces (0.0.0.0).`);
146
- process.exit(1);
147
- }
148
-
149
- console.error("Failed to start server:", err);
150
- process.exit(1);
151
- }
152
-
153
- // 3. Setup CLI Input
154
- const rl = readline.createInterface({
155
- input: process.stdin,
156
- output: process.stdout
157
- });
158
-
159
- rl.on('line', async (input) => {
160
- const command = input.trim().toLowerCase();
161
-
162
- switch (command) {
163
- case 'exit':
164
- case 'stop':
165
- case 'quit':
166
- console.log('Shutting down...');
167
- await service.stop();
168
- process.exit(0);
169
- break;
170
-
171
- case 'reset':
172
- service.resetState();
173
- console.log('✨ Console state reset to defaults.');
174
- break;
175
-
176
- default:
177
- if (command) console.log(`Unknown command: "${command}"`);
178
- break;
179
- }
180
- });
181
-
182
- // Handle Ctrl+C
183
- process.on('SIGINT', async () => {
184
- console.log('\n(SIGINT received)');
185
- await service.stop();
186
- process.exit(0);
187
- });
188
- };
189
-
190
- // Check if main module
191
- const isMainModule = process.argv[1] === fileURLToPath(import.meta.url);
192
- if (isMainModule) {
193
- bootstrap();
194
- }
@@ -1,139 +0,0 @@
1
- /**
2
- * @module x32-simulator
3
- * @description
4
- * The main entry point for the X32 Simulator library.
5
- * This module exports all the necessary classes, interfaces, and types required to
6
- * integrate the X32 simulator into your own Node.js applications or testing frameworks.
7
- *
8
- * It provides access to:
9
- * - The core `SimulationService` for running the simulator.
10
- * - Domain entities like `X32State` and `SubscriptionManager`.
11
- * - Infrastructure components for networking and logging.
12
- * - All OSC-related models and types.
13
- *
14
- * @example
15
- * ```typescript
16
- * import { SimulationService, ConsoleLogger, InMemoryStateRepository, SchemaRegistry, SchemaFactory, UdpNetworkGateway, OscCodec } from '@djodjonx/x32-simulator';
17
- *
18
- * // 1. Setup Dependencies
19
- * const logger = ConsoleLogger.getInstance();
20
- * const schemaRegistry = new SchemaRegistry(new SchemaFactory());
21
- * const codec = new OscCodec(schemaRegistry);
22
- * const gateway = new UdpNetworkGateway(logger, codec);
23
- * const repository = new InMemoryStateRepository(logger, schemaRegistry);
24
- *
25
- * // 2. Initialize Service
26
- * const simulator = new SimulationService(
27
- * gateway,
28
- * logger,
29
- * repository,
30
- * schemaRegistry,
31
- * 10023, // Port
32
- * '127.0.0.1' // Host
33
- * );
34
- *
35
- * // 3. Start
36
- * await simulator.start();
37
- * ```
38
- */
39
-
40
- // Application layer
41
- /**
42
- * The main service class that orchestrates the simulation logic.
43
- */
44
- export { SimulationService } from '../../application/use-cases/SimulationService';
45
-
46
- // Domain entities
47
- /**
48
- * Represents the complete state of the X32 console, including all parameters and faders.
49
- */
50
- export { X32State } from '../../domain/entities/X32State';
51
-
52
- /**
53
- * Manages client subscriptions to OSC updates (e.g. /xremote, /subscribe).
54
- */
55
- export { SubscriptionManager } from '../../domain/entities/SubscriptionManager';
56
-
57
- // Domain models
58
- /**
59
- * Represents a parsed OSC message.
60
- */
61
- export { OscMessage } from '../../domain/models/OscMessage';
62
-
63
- /**
64
- * Utility class for parsing and manipulating X32 OSC address strings.
65
- */
66
- export { X32Address } from '../../domain/models/X32Address';
67
-
68
- /**
69
- * Represents a single parameter node (fader, button, knob) in the X32 state.
70
- */
71
- export { X32Node } from '../../domain/models/X32Node';
72
-
73
- /**
74
- * Handles meter data generation and binary blob creation.
75
- */
76
- export { MeterData } from '../../domain/models/MeterData';
77
-
78
- /**
79
- * Configuration constant defining the number of meters per meter bank.
80
- */
81
- export { METER_COUNTS } from '../../domain/models/MeterConfig';
82
-
83
- /**
84
- * Exports all shared TypeScript types and interfaces used across the domain.
85
- */
86
- export * from '../../domain/models/types';
87
-
88
- // Domain ports (interfaces)
89
- /**
90
- * Interface for the network gateway (UDP communication).
91
- */
92
- export type { INetworkGateway } from '../../domain/ports/INetworkGateway';
93
-
94
- /**
95
- * Interface for the logger.
96
- */
97
- export type { ILogger, LogData } from '../../domain/ports/ILogger';
98
-
99
- /**
100
- * Enum for log categories.
101
- */
102
- export { LogCategory } from '../../domain/ports/ILogger';
103
-
104
- /**
105
- * Interface for the state repository (persistence/storage).
106
- */
107
- export type { IStateRepository } from '../../domain/ports/IStateRepository';
108
-
109
- // Infrastructure layer
110
- /**
111
- * In-memory implementation of the state repository.
112
- */
113
- export { InMemoryStateRepository } from '../../infrastructure/repositories/InMemoryStateRepository';
114
-
115
- /**
116
- * Console-based implementation of the logger.
117
- */
118
- export { ConsoleLogger } from '../../infrastructure/services/ConsoleLogger';
119
-
120
- /**
121
- * Node.js UDP implementation of the network gateway.
122
- */
123
- export { UdpNetworkGateway } from '../../infrastructure/services/UdpNetworkGateway';
124
-
125
- /**
126
- * OSC Codec for encoding and decoding X32-specific OSC packets.
127
- */
128
- export { OscCodec } from '../../infrastructure/mappers/OscCodec';
129
-
130
- // Domain services
131
- /**
132
- * Factory service for creating the initial X32 state schema.
133
- */
134
- export { SchemaFactory } from '../../domain/services/SchemaFactory';
135
-
136
- /**
137
- * Registry service for looking up X32 nodes and definitions.
138
- */
139
- export { SchemaRegistry } from '../../domain/services/SchemaRegistry';
@@ -1,104 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { BroadcastUpdatesUseCase } from '../../../src/application/use-cases/BroadcastUpdatesUseCase';
3
- import { SubscriptionManager } from '../../../src/domain/entities/SubscriptionManager';
4
- import { X32State } from '../../../src/domain/entities/X32State';
5
- import { INetworkGateway } from '../../../src/domain/ports/INetworkGateway';
6
- import { MeterService } from '../../../src/domain/services/MeterService';
7
- import { SchemaRegistry } from '../../../src/domain/services/SchemaRegistry';
8
- import { X32Node } from '../../../src/domain/models/X32Node';
9
-
10
- describe('BroadcastUpdatesUseCase', () => {
11
- it('should broadcast single change to xremote', () => {
12
- const logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() };
13
- const manager = new SubscriptionManager(logger);
14
- manager.addPathSubscriber({ address: '1.2.3.4', port: 1234 }, '/xremote');
15
-
16
- const state = new X32State({});
17
- const gateway = { send: vi.fn() } as unknown as INetworkGateway;
18
- const meterService = new MeterService();
19
- const registry = {} as unknown as SchemaRegistry;
20
-
21
- const useCase = new BroadcastUpdatesUseCase(manager, state, gateway, logger as any, meterService, registry);
22
- useCase.broadcastSingleChange('/ch/01/mix/on', 1);
23
-
24
- expect(gateway.send).toHaveBeenCalledWith(
25
- expect.objectContaining({ address: '1.2.3.4' }),
26
- '/ch/01/mix/on',
27
- [1]
28
- );
29
- });
30
-
31
- it('should execute batch updates', () => {
32
- const logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() };
33
- const manager = new SubscriptionManager(logger);
34
- const client = { address: '1.2.3.4', port: 1234 };
35
-
36
- // Add a batch subscriber
37
- manager.addBatchSubscriber(client, '/batch/alias', ['/mix/fader'], 0, 1, 0);
38
-
39
- const state = new X32State({ '/ch/01/mix/fader': new X32Node('f', 0.5) });
40
- const gateway = { send: vi.fn() } as unknown as INetworkGateway;
41
- const meterService = new MeterService();
42
- const registry = {
43
- getRootFromIndex: vi.fn().mockReturnValue('/ch/01'),
44
- getNode: vi.fn().mockReturnValue(new X32Node('f', 0.0))
45
- } as unknown as SchemaRegistry;
46
-
47
- const useCase = new BroadcastUpdatesUseCase(manager, state, gateway, logger as any, meterService, registry);
48
- useCase.execute();
49
-
50
- expect(gateway.send).toHaveBeenCalledWith(
51
- expect.objectContaining({ address: '1.2.3.4' }),
52
- '/batch/alias',
53
- [expect.any(Buffer)]
54
- );
55
- });
56
-
57
- it('should execute format updates', () => {
58
- const logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() };
59
- const manager = new SubscriptionManager(logger);
60
- const client = { address: '1.2.3.4', port: 1234 };
61
-
62
- // Add a format subscriber: alias, pattern, start, count, factor
63
- manager.addFormatSubscriber(client, '/format/alias', '/ch/*/mix/fader', 1, 2, 1);
64
-
65
- const state = new X32State({
66
- '/ch/01/mix/fader': new X32Node('f', 0.5),
67
- '/ch/02/mix/fader': new X32Node('f', 0.6)
68
- });
69
- const gateway = { send: vi.fn() } as unknown as INetworkGateway;
70
- const meterService = new MeterService();
71
- const registry = {
72
- getNode: vi.fn().mockReturnValue(new X32Node('f', 0.0))
73
- } as unknown as SchemaRegistry;
74
-
75
- const useCase = new BroadcastUpdatesUseCase(manager, state, gateway, logger as any, meterService, registry);
76
- useCase.execute();
77
-
78
- expect(gateway.send).toHaveBeenCalledWith(
79
- expect.objectContaining({ address: '1.2.3.4' }),
80
- '/format/alias',
81
- [expect.any(Buffer)]
82
- );
83
- });
84
-
85
- it('should broadcast meter updates', () => {
86
- const logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() };
87
- const manager = new SubscriptionManager(logger);
88
- manager.addMeterSubscriber({ address: '1.2.3.4', port: 1234 }, '/meters/1');
89
-
90
- const state = new X32State({});
91
- const gateway = { send: vi.fn() } as unknown as INetworkGateway;
92
- const meterService = new MeterService();
93
- const registry = {} as unknown as SchemaRegistry;
94
-
95
- const useCase = new BroadcastUpdatesUseCase(manager, state, gateway, logger as any, meterService, registry);
96
- useCase.execute();
97
-
98
- expect(gateway.send).toHaveBeenCalledWith(
99
- expect.objectContaining({ address: '1.2.3.4' }),
100
- '/meters/1',
101
- [expect.any(Buffer)]
102
- );
103
- });
104
- });
@@ -1,12 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { ManageSessionsUseCase } from '../../../src/application/use-cases/ManageSessionsUseCase';
3
- import { SubscriptionManager } from '../../../src/domain/entities/SubscriptionManager';
4
-
5
- describe('ManageSessionsUseCase', () => {
6
- it('should cleanup sessions', () => {
7
- const manager = { cleanup: vi.fn() } as unknown as SubscriptionManager;
8
- const useCase = new ManageSessionsUseCase(manager);
9
- useCase.cleanup();
10
- expect(manager.cleanup).toHaveBeenCalled();
11
- });
12
- });
@@ -1,49 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { ProcessPacketUseCase } from '../../../src/application/use-cases/ProcessPacketUseCase';
3
- import { OscMessageHandler } from '../../../src/domain/services/OscMessageHandler';
4
- import { INetworkGateway } from '../../../src/domain/ports/INetworkGateway';
5
-
6
- describe('ProcessPacketUseCase', () => {
7
- it('should process message and send replies', () => {
8
- const handler = {
9
- handle: vi.fn().mockReturnValue([{ address: '/reply', args: [1] }])
10
- } as unknown as OscMessageHandler;
11
-
12
- const gateway = {
13
- send: vi.fn()
14
- } as unknown as INetworkGateway;
15
-
16
- const useCase = new ProcessPacketUseCase(handler, gateway);
17
-
18
- useCase.execute(
19
- { oscType: 'message', address: '/test', args: [123] },
20
- { address: '1.2.3.4', port: 1234 }
21
- );
22
-
23
- expect(handler.handle).toHaveBeenCalled();
24
- expect(gateway.send).toHaveBeenCalledWith({ address: '1.2.3.4', port: 1234 }, '/reply', [1]);
25
- });
26
-
27
- it('should process bundles', () => {
28
- const handler = {
29
- handle: vi.fn().mockReturnValue([])
30
- } as unknown as OscMessageHandler;
31
- const gateway = { send: vi.fn() } as unknown as INetworkGateway;
32
- const useCase = new ProcessPacketUseCase(handler, gateway);
33
-
34
- useCase.execute(
35
- {
36
- oscType: 'bundle',
37
- address: '', // Bundles don't have address
38
- args: [],
39
- elements: [
40
- { oscType: 'message', address: '/msg1', args: [] },
41
- { oscType: 'message', address: '/msg2', args: [] }
42
- ]
43
- },
44
- { address: '1.2.3.4', port: 1234 }
45
- );
46
-
47
- expect(handler.handle).toHaveBeenCalledTimes(2);
48
- });
49
- });
@@ -1,77 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- import { SimulationService } from '../../../src/application/use-cases/SimulationService';
3
- import { INetworkGateway } from '../../../src/domain/ports/INetworkGateway';
4
- import { ILogger } from '../../../src/domain/ports/ILogger';
5
- import { IStateRepository } from '../../../src/domain/ports/IStateRepository';
6
- import { SchemaRegistry } from '../../../src/domain/services/SchemaRegistry';
7
- import { X32State } from '../../../src/domain/entities/X32State';
8
- import * as os from 'os';
9
-
10
- vi.mock('os', () => ({
11
- networkInterfaces: vi.fn().mockReturnValue({
12
- eth0: [{ family: 'IPv4', internal: false, address: '192.168.1.1' }]
13
- })
14
- }));
15
-
16
- describe('SimulationService', () => {
17
- let service: SimulationService;
18
- let gateway: INetworkGateway;
19
- let logger: ILogger;
20
- let stateRepo: IStateRepository;
21
- let schemaRegistry: SchemaRegistry;
22
- let state: X32State;
23
-
24
- beforeEach(() => {
25
- gateway = {
26
- start: vi.fn().mockResolvedValue(undefined),
27
- stop: vi.fn().mockResolvedValue(undefined),
28
- send: vi.fn(),
29
- onPacket: vi.fn()
30
- };
31
- logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() };
32
- state = new X32State({});
33
- stateRepo = {
34
- getState: vi.fn().mockReturnValue(state),
35
- reset: vi.fn()
36
- };
37
- schemaRegistry = {
38
- getSchema: vi.fn().mockReturnValue({}),
39
- getNode: vi.fn(),
40
- getAllPaths: vi.fn().mockReturnValue([])
41
- } as unknown as SchemaRegistry;
42
-
43
- // Use 0.0.0.0 to trigger getLocalIp
44
- service = new SimulationService(
45
- gateway,
46
- logger as any,
47
- stateRepo,
48
- schemaRegistry,
49
- 10023,
50
- '0.0.0.0'
51
- );
52
- });
53
-
54
- it('should start the service and detect local IP', async () => {
55
- await service.start();
56
- expect(gateway.start).toHaveBeenCalledWith(10023, '0.0.0.0');
57
- expect(os.networkInterfaces).toHaveBeenCalled();
58
- });
59
-
60
- it('should stop the service', async () => {
61
- await service.start();
62
- await service.stop();
63
- expect(gateway.stop).toHaveBeenCalled();
64
- });
65
-
66
- it('should handle state change events', () => {
67
- // Trigger change on the state
68
- state.emit('change', { address: '/test', value: 1 });
69
- // Should log and broadcast (indirectly verified by coverage)
70
- expect(logger.info).toHaveBeenCalled();
71
- });
72
-
73
- it('should reset state', () => {
74
- service.resetState();
75
- expect(stateRepo.reset).toHaveBeenCalled();
76
- });
77
- });
@@ -1,50 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { SubscriptionManager } from '../../../src/domain/entities/SubscriptionManager';
3
- import { ILogger } from '../../../src/domain/ports/ILogger';
4
-
5
- describe('SubscriptionManager', () => {
6
- const logger: ILogger = {
7
- debug: vi.fn(),
8
- info: vi.fn(),
9
- warn: vi.fn(),
10
- error: vi.fn()
11
- };
12
-
13
- it('should add a path subscriber', () => {
14
- const manager = new SubscriptionManager(logger);
15
- const client = { address: '127.0.0.1', port: 10000 };
16
-
17
- manager.addPathSubscriber(client, '/ch/01/mix/fader');
18
- const subs = manager.getSubscribers();
19
-
20
- expect(subs).toHaveLength(1);
21
- expect(subs[0].type).toBe('path');
22
- expect(subs[0].path).toBe('/ch/01/mix/fader');
23
- });
24
-
25
- it('should remove subscriber', () => {
26
- const manager = new SubscriptionManager(logger);
27
- const client = { address: '127.0.0.1', port: 10000 };
28
-
29
- manager.addPathSubscriber(client, '/test');
30
- manager.removeSubscriber(client, '/test');
31
-
32
- expect(manager.getSubscribers()).toHaveLength(0);
33
- });
34
-
35
- it('should cleanup expired subscribers', () => {
36
- const manager = new SubscriptionManager(logger);
37
- const client = { address: '127.0.0.1', port: 10000 };
38
-
39
- manager.addPathSubscriber(client, '/test');
40
-
41
- // Mock Date.now to fast forward
42
- const originalNow = Date.now;
43
- Date.now = vi.fn(() => originalNow() + 20000); // +20s
44
-
45
- manager.cleanup();
46
- expect(manager.getSubscribers()).toHaveLength(0);
47
-
48
- Date.now = originalNow;
49
- });
50
- });
@@ -1,52 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { X32State } from '../../../src/domain/entities/X32State';
3
- import { X32Node } from '../../../src/domain/models/X32Node';
4
-
5
- describe('X32State', () => {
6
- const schema = {
7
- '/ch/01/mix/fader': new X32Node('f', 0.0),
8
- '/ch/01/mix/on': new X32Node('i', 0),
9
- '/ch/01/grp/mute': new X32Node('i', 0), // needed for mute group logic
10
- };
11
-
12
- it('should initialize with default values', () => {
13
- const state = new X32State(schema);
14
- expect(state.get('/ch/01/mix/fader')).toBe(0.0);
15
- });
16
-
17
- it('should update values and emit events', () => {
18
- const state = new X32State(schema);
19
- const spy = vi.fn();
20
- state.on('change', spy);
21
-
22
- state.set('/ch/01/mix/fader', 0.5);
23
- expect(state.get('/ch/01/mix/fader')).toBe(0.5);
24
- expect(spy).toHaveBeenCalledWith({ address: '/ch/01/mix/fader', value: 0.5 });
25
- });
26
-
27
- it('should reset to defaults', () => {
28
- const state = new X32State(schema);
29
- state.set('/ch/01/mix/fader', 0.8);
30
- state.reset();
31
- expect(state.get('/ch/01/mix/fader')).toBe(0.0);
32
- });
33
-
34
- // Mute Group Logic Test
35
- it('should handle mute group side effects', () => {
36
- const state = new X32State(schema);
37
- // Assign CH 01 to Mute Group 1 (Bit 0)
38
- state.set('/ch/01/grp/mute', 1); // 1 << 0
39
-
40
- const spy = vi.fn();
41
- state.on('change', spy);
42
-
43
- // Turn Mute Group 1 ON (1)
44
- state.handleMuteGroupChange(1, 1);
45
-
46
- // Expect CH 01 Mute to be 0 (Muted? Logic: isOn=1 -> target=0? Check code: isOn=1 -> target=0. Wait, 0 usually means OFF/Muted or ON/Active? In X32 'on' means passing audio. So Mute Group ON means 'on' parameter becomes 0)
47
- // Code: const targetMute = isOn === 1 ? 0 : 1;
48
- // So if MG is ON, channel.on = 0 (Muted). Correct.
49
- expect(state.get('/ch/01/mix/on')).toBe(0);
50
- expect(spy).toHaveBeenCalledWith({ address: '/ch/01/mix/on', value: 0 });
51
- });
52
- });