@djodjonx/x32-simulator 0.0.2 → 0.0.4
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.
- package/CHANGELOG.md +10 -0
- package/README.md +28 -0
- package/dist/{UdpNetworkGateway-BrroQ6-Q.mjs → SchemaRegistry-BRVgnyaA.mjs} +990 -2
- package/dist/{UdpNetworkGateway-Ccdd7Us5.cjs → SchemaRegistry-CfDtw84j.cjs} +1033 -3
- package/dist/index.cjs +160 -6
- package/dist/index.d.cts +61 -11
- package/dist/index.d.mts +61 -11
- package/dist/index.mjs +146 -2
- package/dist/server.cjs +8 -927
- package/dist/server.mjs +1 -920
- package/package.json +5 -1
- package/.commitlintrc.json +0 -3
- package/.github/workflows/publish.yml +0 -38
- package/.husky/commit-msg +0 -1
- package/.husky/pre-commit +0 -1
- package/.oxlintrc.json +0 -56
- package/INSTALL.md +0 -107
- package/docs/OSC-Communication.md +0 -184
- package/docs/X32-INTERNAL.md +0 -262
- package/docs/X32-OSC.pdf +0 -0
- package/docs/behringer-x32-x32-osc-remote-protocol-en-44463.pdf +0 -0
- package/src/application/use-cases/BroadcastUpdatesUseCase.ts +0 -120
- package/src/application/use-cases/ManageSessionsUseCase.ts +0 -9
- package/src/application/use-cases/ProcessPacketUseCase.ts +0 -26
- package/src/application/use-cases/SimulationService.ts +0 -122
- package/src/domain/entities/SubscriptionManager.ts +0 -126
- package/src/domain/entities/X32State.ts +0 -78
- package/src/domain/models/MeterConfig.ts +0 -22
- package/src/domain/models/MeterData.ts +0 -59
- package/src/domain/models/OscMessage.ts +0 -93
- package/src/domain/models/X32Address.ts +0 -78
- package/src/domain/models/X32Node.ts +0 -43
- package/src/domain/models/types.ts +0 -96
- package/src/domain/ports/ILogger.ts +0 -27
- package/src/domain/ports/INetworkGateway.ts +0 -8
- package/src/domain/ports/IStateRepository.ts +0 -16
- package/src/domain/services/MeterService.ts +0 -46
- package/src/domain/services/OscMessageHandler.ts +0 -88
- package/src/domain/services/SchemaFactory.ts +0 -308
- package/src/domain/services/SchemaRegistry.ts +0 -67
- package/src/domain/services/StaticResponseService.ts +0 -52
- package/src/domain/services/strategies/BatchStrategy.ts +0 -74
- package/src/domain/services/strategies/MeterStrategy.ts +0 -45
- package/src/domain/services/strategies/NodeDiscoveryStrategy.ts +0 -36
- package/src/domain/services/strategies/OscCommandStrategy.ts +0 -22
- package/src/domain/services/strategies/StateAccessStrategy.ts +0 -71
- package/src/domain/services/strategies/StaticResponseStrategy.ts +0 -42
- package/src/domain/services/strategies/SubscriptionStrategy.ts +0 -56
- package/src/infrastructure/mappers/OscCodec.ts +0 -54
- package/src/infrastructure/repositories/InMemoryStateRepository.ts +0 -21
- package/src/infrastructure/services/ConsoleLogger.ts +0 -177
- package/src/infrastructure/services/UdpNetworkGateway.ts +0 -71
- package/src/presentation/cli/server.ts +0 -194
- package/src/presentation/library/library.ts +0 -9
- package/tests/application/use-cases/BroadcastUpdatesUseCase.test.ts +0 -104
- package/tests/application/use-cases/ManageSessionsUseCase.test.ts +0 -12
- package/tests/application/use-cases/ProcessPacketUseCase.test.ts +0 -49
- package/tests/application/use-cases/SimulationService.test.ts +0 -77
- package/tests/domain/entities/SubscriptionManager.test.ts +0 -50
- package/tests/domain/entities/X32State.test.ts +0 -52
- package/tests/domain/models/MeterData.test.ts +0 -23
- package/tests/domain/models/OscMessage.test.ts +0 -38
- package/tests/domain/models/X32Address.test.ts +0 -30
- package/tests/domain/models/X32Node.test.ts +0 -30
- package/tests/domain/services/MeterService.test.ts +0 -27
- package/tests/domain/services/OscMessageHandler.test.ts +0 -51
- package/tests/domain/services/SchemaRegistry.test.ts +0 -47
- package/tests/domain/services/StaticResponseService.test.ts +0 -15
- package/tests/domain/services/strategies/BatchStrategy.test.ts +0 -41
- package/tests/domain/services/strategies/MeterStrategy.test.ts +0 -19
- package/tests/domain/services/strategies/NodeDiscoveryStrategy.test.ts +0 -22
- package/tests/domain/services/strategies/StateAccessStrategy.test.ts +0 -49
- package/tests/domain/services/strategies/StaticResponseStrategy.test.ts +0 -15
- package/tests/domain/services/strategies/SubscriptionStrategy.test.ts +0 -45
- package/tests/infrastructure/mappers/OscCodec.test.ts +0 -41
- package/tests/infrastructure/repositories/InMemoryStateRepository.test.ts +0 -29
- package/tests/infrastructure/services/ConsoleLogger.test.ts +0 -74
- package/tests/infrastructure/services/UdpNetworkGateway.test.ts +0 -61
- package/tests/presentation/cli/server.test.ts +0 -178
- package/tests/presentation/library/library.test.ts +0 -13
- package/tsconfig.json +0 -21
- package/tsdown.config.ts +0 -15
- package/vitest.config.ts +0 -9
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
import { BatchStrategy } from '../../../../src/domain/services/strategies/BatchStrategy';
|
|
3
|
-
import { SubscriptionManager } from '../../../../src/domain/entities/SubscriptionManager';
|
|
4
|
-
|
|
5
|
-
describe('BatchStrategy', () => {
|
|
6
|
-
it('should handle /formatsubscribe', () => {
|
|
7
|
-
const logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() };
|
|
8
|
-
const manager = new SubscriptionManager(logger);
|
|
9
|
-
const strategy = new BatchStrategy(manager, logger);
|
|
10
|
-
|
|
11
|
-
// /formatsubscribe, alias, pattern, start, end, factor
|
|
12
|
-
strategy.execute({ address: '/formatsubscribe', args: ['alias', '/ch/*/mix/on', 1, 10, 5] }, { address: '1.2.3.4', port: 1234 });
|
|
13
|
-
expect(manager.getSubscribers()).toHaveLength(1);
|
|
14
|
-
expect(manager.getSubscribers()[0].type).toBe('format');
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it('should handle /batchsubscribe', () => {
|
|
18
|
-
const logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() };
|
|
19
|
-
const manager = new SubscriptionManager(logger);
|
|
20
|
-
const strategy = new BatchStrategy(manager, logger);
|
|
21
|
-
|
|
22
|
-
// /batchsubscribe, alias, path1, path2, ..., int1, int2, ..., factor
|
|
23
|
-
// args: ['alias', '/mix/fader', '/mix/on', 10] (factor=10)
|
|
24
|
-
strategy.execute({ address: '/batchsubscribe', args: ['b_alias', '/mix/fader', '/mix/on', 10] }, { address: '1.2.3.4', port: 1234 });
|
|
25
|
-
|
|
26
|
-
const subs = manager.getSubscribers();
|
|
27
|
-
expect(subs).toHaveLength(1);
|
|
28
|
-
expect(subs[0].type).toBe('batch');
|
|
29
|
-
expect(subs[0].paths).toEqual(['/mix/fader', '/mix/on']);
|
|
30
|
-
expect(subs[0].factor).toBe(10);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it('should return empty if no integer found in batchsubscribe', () => {
|
|
34
|
-
const logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() };
|
|
35
|
-
const manager = new SubscriptionManager(logger);
|
|
36
|
-
const strategy = new BatchStrategy(manager, logger);
|
|
37
|
-
|
|
38
|
-
const replies = strategy.execute({ address: '/batchsubscribe', args: ['alias', 'path'] }, { address: '1.2.3.4', port: 1234 });
|
|
39
|
-
expect(replies).toHaveLength(0);
|
|
40
|
-
});
|
|
41
|
-
});
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
import { MeterStrategy } from '../../../../src/domain/services/strategies/MeterStrategy';
|
|
3
|
-
import { SubscriptionManager } from '../../../../src/domain/entities/SubscriptionManager';
|
|
4
|
-
import { MeterService } from '../../../../src/domain/services/MeterService';
|
|
5
|
-
import { X32State } from '../../../../src/domain/entities/X32State';
|
|
6
|
-
|
|
7
|
-
describe('MeterStrategy', () => {
|
|
8
|
-
it('should handle /meters', () => {
|
|
9
|
-
const logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() };
|
|
10
|
-
const manager = new SubscriptionManager(logger);
|
|
11
|
-
const meterService = new MeterService();
|
|
12
|
-
const state = new X32State({});
|
|
13
|
-
const strategy = new MeterStrategy(manager, state, meterService);
|
|
14
|
-
|
|
15
|
-
strategy.execute({ address: '/meters', args: ['/meters/1'] }, { address: '1.2.3.4', port: 1234 });
|
|
16
|
-
expect(manager.getSubscribers()).toHaveLength(1);
|
|
17
|
-
expect(manager.getSubscribers()[0].type).toBe('meter');
|
|
18
|
-
});
|
|
19
|
-
});
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { NodeDiscoveryStrategy } from '../../../../src/domain/services/strategies/NodeDiscoveryStrategy';
|
|
3
|
-
import { SchemaRegistry } from '../../../../src/domain/services/SchemaRegistry';
|
|
4
|
-
|
|
5
|
-
describe('NodeDiscoveryStrategy', () => {
|
|
6
|
-
it('should list children', () => {
|
|
7
|
-
// Mock registry with getAllPaths
|
|
8
|
-
const registry = {
|
|
9
|
-
getAllPaths: () => ['/ch/01/mix', '/ch/01/config', '/bus/01']
|
|
10
|
-
} as unknown as SchemaRegistry;
|
|
11
|
-
|
|
12
|
-
const strategy = new NodeDiscoveryStrategy(registry);
|
|
13
|
-
|
|
14
|
-
// Query /ch/01
|
|
15
|
-
const replies = strategy.execute({ address: '/node', args: ['/ch/01'] }, { address: '127.0.0.1', port: 10000 });
|
|
16
|
-
|
|
17
|
-
expect(replies).toHaveLength(1);
|
|
18
|
-
expect(replies[0].args).toContain('mix');
|
|
19
|
-
expect(replies[0].args).toContain('config');
|
|
20
|
-
expect(replies[0].args).not.toContain('bus');
|
|
21
|
-
});
|
|
22
|
-
});
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { StateAccessStrategy } from '../../../../src/domain/services/strategies/StateAccessStrategy';
|
|
3
|
-
import { X32State } from '../../../../src/domain/entities/X32State';
|
|
4
|
-
import { SchemaRegistry } from '../../../../src/domain/services/SchemaRegistry';
|
|
5
|
-
import { X32Node } from '../../../../src/domain/models/X32Node';
|
|
6
|
-
|
|
7
|
-
describe('StateAccessStrategy', () => {
|
|
8
|
-
let strategy: StateAccessStrategy;
|
|
9
|
-
let state: X32State;
|
|
10
|
-
|
|
11
|
-
beforeEach(() => {
|
|
12
|
-
// Mini schema for testing
|
|
13
|
-
const schema = {
|
|
14
|
-
'/test/fader': new X32Node('f', 0.0),
|
|
15
|
-
'/config/mute/1': new X32Node('i', 0),
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
// Mock registry to return our mini schema nodes
|
|
19
|
-
const registry = {
|
|
20
|
-
has: (path: string) => path in schema,
|
|
21
|
-
getNode: (path: string) => schema[path as keyof typeof schema]
|
|
22
|
-
} as unknown as SchemaRegistry;
|
|
23
|
-
|
|
24
|
-
state = new X32State(schema);
|
|
25
|
-
|
|
26
|
-
const logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() };
|
|
27
|
-
|
|
28
|
-
strategy = new StateAccessStrategy(state, logger, registry);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it('should handle get requests', () => {
|
|
32
|
-
state.set('/test/fader', 0.5);
|
|
33
|
-
const replies = strategy.execute({ address: '/test/fader', args: [] }, { address: '127.0.0.1', port: 10000 });
|
|
34
|
-
expect(replies).toHaveLength(1);
|
|
35
|
-
expect(replies[0].args[0]).toBe(0.5);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('should handle set requests', () => {
|
|
39
|
-
const replies = strategy.execute({ address: '/test/fader', args: [0.8] }, { address: '127.0.0.1', port: 10000 });
|
|
40
|
-
expect(state.get('/test/fader')).toBe(0.8);
|
|
41
|
-
expect(replies).toHaveLength(0);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('should validate types', () => {
|
|
45
|
-
// Sending string to float
|
|
46
|
-
strategy.execute({ address: '/test/fader', args: ['invalid'] }, { address: '127.0.0.1', port: 10000 });
|
|
47
|
-
expect(state.get('/test/fader')).toBe(0.0); // Should not change
|
|
48
|
-
});
|
|
49
|
-
});
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { StaticResponseStrategy } from '../../../../src/domain/services/strategies/StaticResponseStrategy';
|
|
3
|
-
import { StaticResponseService } from '../../../../src/domain/services/StaticResponseService';
|
|
4
|
-
|
|
5
|
-
describe('StaticResponseStrategy', () => {
|
|
6
|
-
it('should return static response with replacements', () => {
|
|
7
|
-
const service = new StaticResponseService();
|
|
8
|
-
const strategy = new StaticResponseStrategy('10.0.0.1', 'MyMixer', 'X32', service);
|
|
9
|
-
|
|
10
|
-
// /status -> ["active", "{{ip}}", "{{name}}"]
|
|
11
|
-
const replies = strategy.execute({ address: '/status', args: [] }, { address: '127.0.0.1', port: 10000 });
|
|
12
|
-
|
|
13
|
-
expect(replies[0].args).toEqual(['active', '10.0.0.1', 'MyMixer']);
|
|
14
|
-
});
|
|
15
|
-
});
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
import { SubscriptionStrategy } from '../../../../src/domain/services/strategies/SubscriptionStrategy';
|
|
3
|
-
import { SubscriptionManager } from '../../../../src/domain/entities/SubscriptionManager';
|
|
4
|
-
import { X32State } from '../../../../src/domain/entities/X32State';
|
|
5
|
-
import { X32Node } from '../../../../src/domain/models/X32Node';
|
|
6
|
-
|
|
7
|
-
describe('SubscriptionStrategy', () => {
|
|
8
|
-
it('should handle /subscribe', () => {
|
|
9
|
-
const logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() };
|
|
10
|
-
const manager = new SubscriptionManager(logger);
|
|
11
|
-
const state = new X32State({ '/test': new X32Node('i', 123) });
|
|
12
|
-
const strategy = new SubscriptionStrategy(manager, state, logger);
|
|
13
|
-
|
|
14
|
-
const replies = strategy.execute({ address: '/subscribe', args: ['/test', 10] }, { address: '1.2.3.4', port: 1234 });
|
|
15
|
-
|
|
16
|
-
// Should add subscriber
|
|
17
|
-
expect(manager.getSubscribers()).toHaveLength(1);
|
|
18
|
-
// Should return current value
|
|
19
|
-
expect(replies[0].args[0]).toBe(123);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('should handle /xremote', () => {
|
|
23
|
-
const logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() };
|
|
24
|
-
const manager = new SubscriptionManager(logger);
|
|
25
|
-
const state = new X32State({});
|
|
26
|
-
const strategy = new SubscriptionStrategy(manager, state, logger);
|
|
27
|
-
|
|
28
|
-
strategy.execute({ address: '/xremote', args: [] }, { address: '1.2.3.4', port: 1234 });
|
|
29
|
-
expect(manager.getSubscribers()).toHaveLength(1);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it('should handle /unsubscribe', () => {
|
|
33
|
-
const logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() };
|
|
34
|
-
const manager = new SubscriptionManager(logger);
|
|
35
|
-
const state = new X32State({});
|
|
36
|
-
const strategy = new SubscriptionStrategy(manager, state, logger);
|
|
37
|
-
const client = { address: '1.2.3.4', port: 1234 };
|
|
38
|
-
|
|
39
|
-
manager.addPathSubscriber(client, '/test');
|
|
40
|
-
expect(manager.getSubscribers()).toHaveLength(1);
|
|
41
|
-
|
|
42
|
-
strategy.execute({ address: '/unsubscribe', args: ['/test'] }, client);
|
|
43
|
-
expect(manager.getSubscribers()).toHaveLength(0);
|
|
44
|
-
});
|
|
45
|
-
});
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { OscCodec } from '../../../src/infrastructure/mappers/OscCodec';
|
|
3
|
-
import { SchemaRegistry } from '../../../src/domain/services/SchemaRegistry';
|
|
4
|
-
import { X32Node } from '../../../src/domain/models/X32Node';
|
|
5
|
-
|
|
6
|
-
describe('OscCodec', () => {
|
|
7
|
-
const registry = {
|
|
8
|
-
getNode: (path: string) => {
|
|
9
|
-
if (path === '/test/int') return new X32Node('i', 0);
|
|
10
|
-
if (path === '/test/float') return new X32Node('f', 0.0);
|
|
11
|
-
if (path === '/test/string') return new X32Node('s', '');
|
|
12
|
-
return undefined;
|
|
13
|
-
}
|
|
14
|
-
} as unknown as SchemaRegistry;
|
|
15
|
-
|
|
16
|
-
const codec = new OscCodec(registry);
|
|
17
|
-
|
|
18
|
-
it('should encode message with schema inference', () => {
|
|
19
|
-
// Int
|
|
20
|
-
const buf1 = codec.encode('/test/int', [123]);
|
|
21
|
-
expect(Buffer.isBuffer(buf1)).toBe(true);
|
|
22
|
-
|
|
23
|
-
// Float
|
|
24
|
-
const buf2 = codec.encode('/test/float', [0.5]);
|
|
25
|
-
expect(Buffer.isBuffer(buf2)).toBe(true);
|
|
26
|
-
|
|
27
|
-
// String
|
|
28
|
-
const buf3 = codec.encode('/test/string', ['hello']);
|
|
29
|
-
expect(Buffer.isBuffer(buf3)).toBe(true);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it('should handle raw typed args in encode', () => {
|
|
33
|
-
const buf = codec.encode('/any', [{ type: 'i', value: 10 }]);
|
|
34
|
-
expect(Buffer.isBuffer(buf)).toBe(true);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('should handle buffers in encode', () => {
|
|
38
|
-
const buf = codec.encode('/any', [Buffer.alloc(4)]);
|
|
39
|
-
expect(Buffer.isBuffer(buf)).toBe(true);
|
|
40
|
-
});
|
|
41
|
-
});
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
import { InMemoryStateRepository } from '../../../src/infrastructure/repositories/InMemoryStateRepository';
|
|
3
|
-
import { SchemaRegistry } from '../../../src/domain/services/SchemaRegistry';
|
|
4
|
-
import { ILogger } from '../../../src/domain/ports/ILogger';
|
|
5
|
-
|
|
6
|
-
describe('InMemoryStateRepository', () => {
|
|
7
|
-
it('should initialize and provide state', () => {
|
|
8
|
-
const logger: ILogger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() };
|
|
9
|
-
const schemaRegistry = {
|
|
10
|
-
getSchema: vi.fn().mockReturnValue({})
|
|
11
|
-
} as unknown as SchemaRegistry;
|
|
12
|
-
|
|
13
|
-
const repo = new InMemoryStateRepository(logger, schemaRegistry);
|
|
14
|
-
const state = repo.getState();
|
|
15
|
-
|
|
16
|
-
expect(state).toBeDefined();
|
|
17
|
-
expect(repo.getState()).toBe(state); // Singleton check
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it('should reset state', () => {
|
|
21
|
-
const logger: ILogger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() };
|
|
22
|
-
const schemaRegistry = { getSchema: vi.fn().mockReturnValue({}) } as unknown as SchemaRegistry;
|
|
23
|
-
const repo = new InMemoryStateRepository(logger, schemaRegistry);
|
|
24
|
-
|
|
25
|
-
// We can't easily see internal reset without mocking X32State,
|
|
26
|
-
// but we can verify it doesn't throw.
|
|
27
|
-
expect(() => repo.reset()).not.toThrow();
|
|
28
|
-
});
|
|
29
|
-
});
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { ConsoleLogger, LogLevel } from '../../../src/infrastructure/services/ConsoleLogger';
|
|
3
|
-
import { LogCategory } from '../../../src/domain/ports/ILogger';
|
|
4
|
-
|
|
5
|
-
describe('ConsoleLogger', () => {
|
|
6
|
-
let logger: ConsoleLogger;
|
|
7
|
-
|
|
8
|
-
beforeEach(() => {
|
|
9
|
-
logger = new ConsoleLogger();
|
|
10
|
-
vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
11
|
-
vi.clearAllMocks();
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it('should log messages at all levels', () => {
|
|
15
|
-
logger.setLevel(LogLevel.DEBUG);
|
|
16
|
-
logger.debug(LogCategory.SYSTEM, 'debug');
|
|
17
|
-
logger.info(LogCategory.SYSTEM, 'info');
|
|
18
|
-
logger.warn(LogCategory.SYSTEM, 'warn');
|
|
19
|
-
logger.error(LogCategory.SYSTEM, 'error');
|
|
20
|
-
expect(console.log).toHaveBeenCalledTimes(4);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('should not log if level is higher', () => {
|
|
24
|
-
logger.setLevel(LogLevel.ERROR);
|
|
25
|
-
logger.info(LogCategory.SYSTEM, 'test info');
|
|
26
|
-
expect(console.log).not.toHaveBeenCalled();
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('should format message with color codes', () => {
|
|
30
|
-
logger.setLevel(LogLevel.INFO);
|
|
31
|
-
logger.info(LogCategory.SYSTEM, 'hello');
|
|
32
|
-
const call = vi.mocked(console.log).mock.calls[0][0];
|
|
33
|
-
expect(call).toContain('[SYSTEM ]');
|
|
34
|
-
expect(call).toContain('hello');
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('should handle data metadata', () => {
|
|
38
|
-
logger.info(LogCategory.SYSTEM, 'msg', { foo: 'bar' });
|
|
39
|
-
expect(console.log).toHaveBeenCalledWith(expect.stringContaining('{"foo":"bar"}'));
|
|
40
|
-
|
|
41
|
-
// Buffer data
|
|
42
|
-
logger.info(LogCategory.SYSTEM, 'buf', Buffer.from('abc'));
|
|
43
|
-
expect(console.log).toHaveBeenCalledWith(expect.stringContaining('<Buffer 3b>'));
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('should enable and disable categories', () => {
|
|
47
|
-
logger.disableCategory(LogCategory.SYSTEM);
|
|
48
|
-
logger.info(LogCategory.SYSTEM, 'hidden');
|
|
49
|
-
expect(console.log).not.toHaveBeenCalled();
|
|
50
|
-
|
|
51
|
-
logger.enableCategory(LogCategory.SYSTEM);
|
|
52
|
-
logger.info(LogCategory.SYSTEM, 'shown');
|
|
53
|
-
expect(console.log).toHaveBeenCalled();
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it('should singleton instance', () => {
|
|
57
|
-
const instance = ConsoleLogger.getInstance();
|
|
58
|
-
expect(instance).toBeInstanceOf(ConsoleLogger);
|
|
59
|
-
expect(ConsoleLogger.getInstance()).toBe(instance);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('should hide messages matching patterns', () => {
|
|
63
|
-
process.env.HIDDEN_LOG = 'secret,private';
|
|
64
|
-
const loggerWithEnv = new ConsoleLogger();
|
|
65
|
-
|
|
66
|
-
loggerWithEnv.info(LogCategory.SYSTEM, 'this is secret');
|
|
67
|
-
expect(console.log).not.toHaveBeenCalled();
|
|
68
|
-
|
|
69
|
-
loggerWithEnv.info(LogCategory.SYSTEM, 'this is public');
|
|
70
|
-
expect(console.log).toHaveBeenCalled();
|
|
71
|
-
|
|
72
|
-
delete process.env.HIDDEN_LOG;
|
|
73
|
-
});
|
|
74
|
-
});
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { UdpNetworkGateway } from '../../../src/infrastructure/services/UdpNetworkGateway';
|
|
3
|
-
import { ILogger } from '../../../src/domain/ports/ILogger';
|
|
4
|
-
import { OscCodec } from '../../../src/infrastructure/mappers/OscCodec';
|
|
5
|
-
import * as dgram from 'dgram';
|
|
6
|
-
|
|
7
|
-
let messageCallback: Function;
|
|
8
|
-
|
|
9
|
-
vi.mock('dgram', () => ({
|
|
10
|
-
createSocket: vi.fn(() => ({
|
|
11
|
-
bind: vi.fn((port, ip, cb) => cb && cb()),
|
|
12
|
-
on: vi.fn((event, cb) => {
|
|
13
|
-
if (event === 'message') messageCallback = cb;
|
|
14
|
-
}),
|
|
15
|
-
send: vi.fn(),
|
|
16
|
-
close: vi.fn((cb) => cb && cb()),
|
|
17
|
-
}))
|
|
18
|
-
}));
|
|
19
|
-
|
|
20
|
-
describe('UdpNetworkGateway', () => {
|
|
21
|
-
let gateway: UdpNetworkGateway;
|
|
22
|
-
let logger: ILogger;
|
|
23
|
-
let codec: OscCodec;
|
|
24
|
-
|
|
25
|
-
beforeEach(() => {
|
|
26
|
-
logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() };
|
|
27
|
-
codec = {
|
|
28
|
-
encode: vi.fn().mockReturnValue(Buffer.from('encoded')),
|
|
29
|
-
decode: vi.fn().mockReturnValue({ address: '/decoded', args: [] })
|
|
30
|
-
} as unknown as OscCodec;
|
|
31
|
-
gateway = new UdpNetworkGateway(logger, codec);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it('should start and bind socket', async () => {
|
|
35
|
-
await gateway.start(10023, '0.0.0.0');
|
|
36
|
-
expect(dgram.createSocket).toHaveBeenCalledWith('udp4');
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('should send packets', async () => {
|
|
40
|
-
await gateway.start(10023, '0.0.0.0');
|
|
41
|
-
gateway.send({ address: '1.2.3.4', port: 1234 }, '/test', [1]);
|
|
42
|
-
expect(codec.encode).toHaveBeenCalledWith('/test', [1]);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('should receive and decode packets', async () => {
|
|
46
|
-
const spy = vi.fn();
|
|
47
|
-
gateway.onPacket(spy);
|
|
48
|
-
await gateway.start(10023, '0.0.0.0');
|
|
49
|
-
|
|
50
|
-
// Simulate incoming message
|
|
51
|
-
messageCallback(Buffer.from('raw'), { address: '5.6.7.8', port: 5678 });
|
|
52
|
-
|
|
53
|
-
expect(codec.decode).toHaveBeenCalled();
|
|
54
|
-
expect(spy).toHaveBeenCalledWith(expect.anything(), expect.objectContaining({ address: '5.6.7.8' }));
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('should stop', async () => {
|
|
58
|
-
await gateway.start(10023, '0.0.0.0');
|
|
59
|
-
await gateway.stop();
|
|
60
|
-
});
|
|
61
|
-
});
|
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import * as fs from 'fs';
|
|
3
|
-
|
|
4
|
-
const mocks = vi.hoisted(() => ({
|
|
5
|
-
serviceInstance: {
|
|
6
|
-
start: vi.fn().mockResolvedValue(undefined),
|
|
7
|
-
stop: vi.fn().mockResolvedValue(undefined),
|
|
8
|
-
resetState: vi.fn()
|
|
9
|
-
},
|
|
10
|
-
rl: {
|
|
11
|
-
on: vi.fn(),
|
|
12
|
-
close: vi.fn()
|
|
13
|
-
}
|
|
14
|
-
}));
|
|
15
|
-
|
|
16
|
-
// Mock fs
|
|
17
|
-
vi.mock('fs', () => ({
|
|
18
|
-
existsSync: vi.fn(),
|
|
19
|
-
readFileSync: vi.fn()
|
|
20
|
-
}));
|
|
21
|
-
|
|
22
|
-
// Mock SimulationService
|
|
23
|
-
vi.mock('../../../src/application/use-cases/SimulationService', () => {
|
|
24
|
-
const MockService = vi.fn();
|
|
25
|
-
MockService.prototype.start = mocks.serviceInstance.start;
|
|
26
|
-
MockService.prototype.stop = mocks.serviceInstance.stop;
|
|
27
|
-
MockService.prototype.resetState = mocks.serviceInstance.resetState;
|
|
28
|
-
return {
|
|
29
|
-
SimulationService: MockService
|
|
30
|
-
};
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
// Mock ConsoleLogger
|
|
34
|
-
vi.mock('../../../src/infrastructure/services/ConsoleLogger', () => ({
|
|
35
|
-
ConsoleLogger: {
|
|
36
|
-
getInstance: vi.fn().mockReturnValue({ setLevel: vi.fn() })
|
|
37
|
-
},
|
|
38
|
-
LogLevel: { DEBUG: 0 }
|
|
39
|
-
}));
|
|
40
|
-
|
|
41
|
-
// Mock readline
|
|
42
|
-
vi.mock('readline', () => ({
|
|
43
|
-
createInterface: vi.fn().mockReturnValue(mocks.rl)
|
|
44
|
-
}));
|
|
45
|
-
|
|
46
|
-
import * as Server from '../../../src/presentation/cli/server';
|
|
47
|
-
import { SimulationService } from '../../../src/application/use-cases/SimulationService';
|
|
48
|
-
|
|
49
|
-
describe('CLI Server', () => {
|
|
50
|
-
beforeEach(() => {
|
|
51
|
-
vi.clearAllMocks();
|
|
52
|
-
vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
53
|
-
vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
54
|
-
vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
55
|
-
vi.spyOn(process, 'exit').mockImplementation(() => undefined as never);
|
|
56
|
-
vi.spyOn(process, 'on').mockImplementation(() => process);
|
|
57
|
-
|
|
58
|
-
// Reset specific mock implementations if needed
|
|
59
|
-
mocks.serviceInstance.start.mockResolvedValue(undefined);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
afterEach(() => {
|
|
63
|
-
vi.restoreAllMocks();
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
describe('parseArgs', () => {
|
|
67
|
-
it('should parse port and host flags', () => {
|
|
68
|
-
const args = ['node', 'server.ts', '--port', '1234', '--host', '1.2.3.4'];
|
|
69
|
-
const result = Server.parseArgs(args);
|
|
70
|
-
expect(result.PORT).toBe(1234);
|
|
71
|
-
expect(result.HOST).toBe('1.2.3.4');
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it('should parse short flags', () => {
|
|
75
|
-
const args = ['node', 'server.ts', '-p', '1234', '-h', '1.2.3.4'];
|
|
76
|
-
const result = Server.parseArgs(args);
|
|
77
|
-
expect(result.PORT).toBe(1234);
|
|
78
|
-
expect(result.HOST).toBe('1.2.3.4');
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it('should parse positional arguments', () => {
|
|
82
|
-
const args = ['node', 'server.ts', '1.2.3.4', '1234'];
|
|
83
|
-
const result = Server.parseArgs(args);
|
|
84
|
-
expect(result.PORT).toBe(1234);
|
|
85
|
-
expect(result.HOST).toBe('1.2.3.4');
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it('should use defaults', () => {
|
|
89
|
-
const args = ['node', 'server.ts'];
|
|
90
|
-
const result = Server.parseArgs(args);
|
|
91
|
-
expect(result.PORT).toBe(10023);
|
|
92
|
-
expect(result.HOST).toBe('0.0.0.0');
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
describe('loadEnv', () => {
|
|
97
|
-
it('should load .env file', () => {
|
|
98
|
-
vi.mocked(fs.existsSync).mockReturnValue(true);
|
|
99
|
-
vi.mocked(fs.readFileSync).mockReturnValue('X32_PORT=9999\nX32_IP=10.0.0.1');
|
|
100
|
-
|
|
101
|
-
Server.loadEnv();
|
|
102
|
-
|
|
103
|
-
expect(process.env.X32_PORT).toBe('9999');
|
|
104
|
-
expect(process.env.X32_IP).toBe('10.0.0.1');
|
|
105
|
-
|
|
106
|
-
delete process.env.X32_PORT;
|
|
107
|
-
delete process.env.X32_IP;
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it('should handle errors gracefully', () => {
|
|
111
|
-
vi.mocked(fs.existsSync).mockReturnValue(true);
|
|
112
|
-
vi.mocked(fs.readFileSync).mockImplementation(() => { throw new Error('Fail'); });
|
|
113
|
-
|
|
114
|
-
Server.loadEnv();
|
|
115
|
-
|
|
116
|
-
expect(console.warn).toHaveBeenCalledWith(expect.stringContaining('Failed to load'), expect.any(String));
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
describe('bootstrap', () => {
|
|
121
|
-
it('should start simulation service', async () => {
|
|
122
|
-
await Server.bootstrap();
|
|
123
|
-
expect(SimulationService).toHaveBeenCalled();
|
|
124
|
-
expect(console.log).toHaveBeenCalledWith(expect.stringContaining('X32 SIMULATION SERVER ACTIVE'));
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it('should handle EADDRNOTAVAIL error', async () => {
|
|
128
|
-
const error = new Error('Bind failed');
|
|
129
|
-
(error as any).code = 'EADDRNOTAVAIL';
|
|
130
|
-
mocks.serviceInstance.start.mockRejectedValueOnce(error);
|
|
131
|
-
|
|
132
|
-
// Mock platform
|
|
133
|
-
Object.defineProperty(process, 'platform', { value: 'darwin', configurable: true });
|
|
134
|
-
|
|
135
|
-
await Server.bootstrap();
|
|
136
|
-
expect(console.error).toHaveBeenCalledWith(expect.stringContaining('FAILED TO BIND'));
|
|
137
|
-
expect(process.exit).toHaveBeenCalledWith(1);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
it('should handle generic startup errors', async () => {
|
|
141
|
-
mocks.serviceInstance.start.mockRejectedValueOnce(new Error('Generic fail'));
|
|
142
|
-
await Server.bootstrap();
|
|
143
|
-
expect(console.error).toHaveBeenCalledWith('Failed to start server:', expect.any(Error));
|
|
144
|
-
expect(process.exit).toHaveBeenCalledWith(1);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it('should handle CLI commands', async () => {
|
|
148
|
-
await Server.bootstrap();
|
|
149
|
-
|
|
150
|
-
const lineHandler = mocks.rl.on.mock.calls.find(call => call[0] === 'line')?.[1];
|
|
151
|
-
expect(lineHandler).toBeDefined();
|
|
152
|
-
|
|
153
|
-
await lineHandler('reset');
|
|
154
|
-
expect(mocks.serviceInstance.resetState).toHaveBeenCalled();
|
|
155
|
-
|
|
156
|
-
await lineHandler('stop');
|
|
157
|
-
expect(mocks.serviceInstance.stop).toHaveBeenCalled();
|
|
158
|
-
expect(process.exit).toHaveBeenCalledWith(0);
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
it('should handle unknown CLI commands', async () => {
|
|
162
|
-
await Server.bootstrap();
|
|
163
|
-
const lineHandler = mocks.rl.on.mock.calls.find(call => call[0] === 'line')?.[1];
|
|
164
|
-
await lineHandler('unknown');
|
|
165
|
-
expect(console.log).toHaveBeenCalledWith(expect.stringContaining('Unknown command'));
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
it('should handle SIGINT', async () => {
|
|
169
|
-
await Server.bootstrap();
|
|
170
|
-
const sigintHandler = vi.mocked(process.on).mock.calls.find(call => call[0] === 'SIGINT')?.[1] as Function;
|
|
171
|
-
expect(sigintHandler).toBeDefined();
|
|
172
|
-
|
|
173
|
-
await sigintHandler();
|
|
174
|
-
expect(mocks.serviceInstance.stop).toHaveBeenCalled();
|
|
175
|
-
expect(process.exit).toHaveBeenCalledWith(0);
|
|
176
|
-
});
|
|
177
|
-
});
|
|
178
|
-
});
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import * as Lib from '../../../src/presentation/library/library';
|
|
3
|
-
|
|
4
|
-
describe('Library Entry', () => {
|
|
5
|
-
it('should export all public members', () => {
|
|
6
|
-
expect(Lib.SimulationService).toBeDefined();
|
|
7
|
-
expect(Lib.ConsoleLogger).toBeDefined();
|
|
8
|
-
expect(Lib.UdpNetworkGateway).toBeDefined();
|
|
9
|
-
expect(Lib.InMemoryStateRepository).toBeDefined();
|
|
10
|
-
expect(Lib.LogCategory).toBeDefined();
|
|
11
|
-
// Types aren't runtime values, so we can't check them with toBeDefined(), but the import * works.
|
|
12
|
-
});
|
|
13
|
-
});
|
package/tsconfig.json
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ESNext",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"moduleResolution": "Bundler",
|
|
6
|
-
"lib": ["ESNext", "DOM"],
|
|
7
|
-
"strict": true,
|
|
8
|
-
"declaration": true,
|
|
9
|
-
"removeComments": true,
|
|
10
|
-
"esModuleInterop": true,
|
|
11
|
-
"skipLibCheck": true,
|
|
12
|
-
"forceConsistentCasingInFileNames": true,
|
|
13
|
-
"outDir": "dist",
|
|
14
|
-
"baseUrl": ".",
|
|
15
|
-
"paths": {
|
|
16
|
-
"@/*": ["src/*"]
|
|
17
|
-
}
|
|
18
|
-
},
|
|
19
|
-
"include": ["src/**/*", "tests/**/*"],
|
|
20
|
-
"exclude": ["node_modules", "dist"]
|
|
21
|
-
}
|
package/tsdown.config.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { defineConfig } from 'tsdown';
|
|
2
|
-
|
|
3
|
-
export default defineConfig({
|
|
4
|
-
clean: true,
|
|
5
|
-
dts: true,
|
|
6
|
-
entry: {
|
|
7
|
-
index: 'src/presentation/library/library.ts',
|
|
8
|
-
server: 'src/presentation/cli/server.ts',
|
|
9
|
-
},
|
|
10
|
-
format: ['cjs', 'esm'],
|
|
11
|
-
platform: 'node',
|
|
12
|
-
// Bundle all dependencies
|
|
13
|
-
// @ts-ignore
|
|
14
|
-
noExternal: [/(.*)/],
|
|
15
|
-
});
|