@loop_ouroboros/mcp-hub-lite 1.2.8 → 1.3.0
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 +50 -0
- package/dist/client/assets/{HomeView-CgEri1kD.js → HomeView-Bi2bkUKf.js} +1 -1
- package/dist/client/assets/{ResourceDetailView-DUJZbegl.css → ResourceDetailView-BkTSg91z.css} +1 -1
- package/dist/client/assets/ResourceDetailView-DyuSovH9.js +1 -0
- package/dist/client/assets/ResourcesView-CU0VbNy5.js +1 -0
- package/dist/client/assets/ResourcesView-zgV8Nq7w.css +1 -0
- package/dist/client/assets/{ServerDashboard-B3O-crvv.js → ServerDashboard-BGyyZAti.js} +1 -1
- package/dist/client/assets/{ServerDetail-CXg8rI3q.css → ServerDetail-CPNAFBPM.css} +1 -1
- package/dist/client/assets/ServerDetail-bcQ8BVXR.js +2 -0
- package/dist/client/assets/{ServerListView-SlZN8ppC.js → ServerListView-yQPVJFHG.js} +1 -1
- package/dist/client/assets/{ServerStatusTags.vue_vue_type_script_setup_true_lang-DmGg4uuV.js → ServerStatusTags.vue_vue_type_script_setup_true_lang-C8gQlxGE.js} +1 -1
- package/dist/client/assets/{SettingsView-D8fiOG0O.js → SettingsView-B1DxbFP3.js} +1 -1
- package/dist/client/assets/ToolCallDialog-BQ9UJZ_-.css +1 -0
- package/dist/client/assets/ToolCallDialog-DEapCO06.js +1 -0
- package/dist/client/assets/ToolsView-DA0u_bCw.js +1 -0
- package/dist/client/assets/ToolsView-cO61nMNr.css +1 -0
- package/dist/client/assets/{_baseClone-BYxCbA_9.js → _baseClone-B991Lvrt.js} +1 -1
- package/dist/client/assets/{el-form-item-ySymCPMr.js → el-form-item-DfWq_kSy.js} +1 -1
- package/dist/client/assets/{el-input-C85p6Nqj.js → el-input-5YzZrwir.js} +1 -1
- package/dist/client/assets/{el-loading-DIjKEx81.js → el-loading-DE3FcxNH.js} +1 -1
- package/dist/client/assets/{el-overlay-B_CxiSem.js → el-overlay-BTeTueuN.js} +1 -1
- package/dist/client/assets/{el-radio-group-BjkTCPRf.js → el-radio-group-Y1E2bxIW.js} +1 -1
- package/dist/client/assets/{el-skeleton-item-CupTKK6n.js → el-skeleton-item-DhgR50Jx.js} +1 -1
- package/dist/client/assets/{el-switch-BosIJ9jf.js → el-switch-fF--nMSD.js} +1 -1
- package/dist/client/assets/{el-tab-pane-BydxdJoc.js → el-tab-pane-rvS_KTwP.js} +1 -1
- package/dist/client/assets/{el-table-column-DV5TZOUW.js → el-table-column-B1O8mY47.js} +1 -1
- package/dist/client/assets/{index-xJkq2euk.css → index-Bzz3tYbS.css} +1 -1
- package/dist/client/assets/index-DkqV9kH4.js +2 -0
- package/dist/client/assets/{omit-DxDGRttI.js → omit-BIIebEYo.js} +1 -1
- package/dist/client/assets/{raf-Y9AoxecD.js → raf-Cj-gATZv.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/shared/models/constants.d.ts +3 -0
- package/dist/server/shared/models/constants.d.ts.map +1 -0
- package/dist/server/shared/models/constants.js +2 -0
- package/dist/server/shared/models/index.d.ts +1 -0
- package/dist/server/shared/models/index.d.ts.map +1 -1
- package/dist/server/shared/models/index.js +1 -0
- package/dist/server/shared/types/index.d.ts +0 -1
- package/dist/server/shared/types/index.d.ts.map +1 -1
- package/dist/server/shared/types/index.js +0 -1
- package/dist/server/src/api/mcp/debug-response-wrapper.js +1 -1
- package/dist/server/src/api/mcp/gateway.d.ts.map +1 -1
- package/dist/server/src/api/mcp/gateway.js +23 -41
- package/dist/server/src/api/web/hub-tools.d.ts.map +1 -1
- package/dist/server/src/api/web/hub-tools.js +11 -0
- package/dist/server/src/api/web/mcp-status.js +2 -2
- package/dist/server/src/api/web/search.d.ts +2 -1
- package/dist/server/src/api/web/search.d.ts.map +1 -1
- package/dist/server/src/api/web/search.js +23 -10
- package/dist/server/src/api/web/servers.js +1 -1
- package/dist/server/src/api/ws/events.js +1 -1
- package/dist/server/src/api/ws/ws-handler.js +1 -1
- package/dist/server/src/app.js +1 -1
- package/dist/server/src/cli/commands/tool-use.d.ts +10 -3
- package/dist/server/src/cli/commands/tool-use.d.ts.map +1 -1
- package/dist/server/src/cli/commands/tool-use.js +69 -30
- package/dist/server/src/config/config-change-logger.js +1 -1
- package/dist/server/src/config/config-loader.js +1 -1
- package/dist/server/src/config/config-manager.js +1 -1
- package/dist/server/src/config/config-migrator.d.ts +4 -48
- package/dist/server/src/config/config-migrator.d.ts.map +1 -1
- package/dist/server/src/config/config-migrator.js +2 -103
- package/dist/server/src/config/config-saver.js +1 -1
- package/dist/server/src/config/server-config-manager.js +1 -1
- package/dist/server/src/models/event.model.d.ts +0 -98
- package/dist/server/src/models/event.model.d.ts.map +1 -1
- package/dist/server/src/models/event.model.js +0 -4
- package/dist/server/src/models/server.model.d.ts +0 -2
- package/dist/server/src/models/server.model.d.ts.map +1 -1
- package/dist/server/src/models/system-tools.constants.d.ts +8 -3
- package/dist/server/src/models/system-tools.constants.d.ts.map +1 -1
- package/dist/server/src/models/system-tools.constants.js +5 -2
- package/dist/server/src/pid/manager.js +1 -1
- package/dist/server/src/pid/types.d.ts +0 -5
- package/dist/server/src/pid/types.d.ts.map +1 -1
- package/dist/server/src/server/dev-server.js +2 -2
- package/dist/server/src/server/runner.js +2 -2
- package/dist/server/src/server/startup.js +2 -2
- package/dist/server/src/services/connection/connection-manager.d.ts.map +1 -1
- package/dist/server/src/services/connection/connection-manager.js +16 -21
- package/dist/server/src/services/connection/tool-cache.d.ts.map +1 -1
- package/dist/server/src/services/connection/tool-cache.js +10 -8
- package/dist/server/src/services/event-bus.service.d.ts +3 -1
- package/dist/server/src/services/event-bus.service.d.ts.map +1 -1
- package/dist/server/src/services/event-bus.service.js +1 -0
- package/dist/server/src/services/gateway/gateway.service.d.ts +1 -0
- package/dist/server/src/services/gateway/gateway.service.d.ts.map +1 -1
- package/dist/server/src/services/gateway/gateway.service.js +29 -7
- package/dist/server/src/services/gateway/request-handlers/call-tool-handler.d.ts +1 -2
- package/dist/server/src/services/gateway/request-handlers/call-tool-handler.d.ts.map +1 -1
- package/dist/server/src/services/gateway/request-handlers/call-tool-handler.js +24 -13
- package/dist/server/src/services/gateway/request-handlers/resources-handler.d.ts.map +1 -1
- package/dist/server/src/services/gateway/request-handlers/resources-handler.js +7 -3
- package/dist/server/src/services/gateway/request-handlers/system-tools-handler.d.ts.map +1 -1
- package/dist/server/src/services/gateway/request-handlers/system-tools-handler.js +28 -1
- package/dist/server/src/services/gateway/tool-list-generator.d.ts +14 -19
- package/dist/server/src/services/gateway/tool-list-generator.d.ts.map +1 -1
- package/dist/server/src/services/gateway/tool-list-generator.js +221 -80
- package/dist/server/src/services/hub-manager.service.d.ts.map +1 -1
- package/dist/server/src/services/hub-manager.service.js +21 -4
- package/dist/server/src/services/hub-tools/instance-selector.js +1 -1
- package/dist/server/src/services/hub-tools/resource-generator.d.ts +0 -21
- package/dist/server/src/services/hub-tools/resource-generator.d.ts.map +1 -1
- package/dist/server/src/services/hub-tools/resource-generator.js +15 -15
- package/dist/server/src/services/hub-tools/server-selector.js +1 -1
- package/dist/server/src/services/hub-tools/system-tool-definitions.d.ts +1 -0
- package/dist/server/src/services/hub-tools/system-tool-definitions.d.ts.map +1 -1
- package/dist/server/src/services/hub-tools/system-tool-definitions.js +25 -1
- package/dist/server/src/services/hub-tools.service.d.ts +21 -4
- package/dist/server/src/services/hub-tools.service.d.ts.map +1 -1
- package/dist/server/src/services/hub-tools.service.js +74 -13
- package/dist/server/src/services/log-storage.service.js +1 -1
- package/dist/server/src/services/system-tool-handler.d.ts.map +1 -1
- package/dist/server/src/services/system-tool-handler.js +10 -2
- package/dist/server/src/utils/error-handler.js +1 -1
- package/dist/server/src/utils/index.d.ts +1 -1
- package/dist/server/src/utils/index.d.ts.map +1 -1
- package/dist/server/src/utils/index.js +1 -1
- package/dist/server/src/utils/instance-id.d.ts +0 -8
- package/dist/server/src/utils/instance-id.d.ts.map +1 -1
- package/dist/server/src/utils/instance-id.js +1 -1
- package/dist/server/src/utils/json-utils.js +4 -4
- package/dist/server/src/utils/log-rotator.d.ts +0 -15
- package/dist/server/src/utils/log-rotator.d.ts.map +1 -1
- package/dist/server/src/utils/log-rotator.js +0 -18
- package/dist/server/src/utils/logger/index.d.ts +0 -22
- package/dist/server/src/utils/logger/index.d.ts.map +1 -1
- package/dist/server/src/utils/logger/index.js +0 -29
- package/dist/server/src/utils/port-checker.js +1 -1
- package/dist/server/src/utils/sort-utils.d.ts +0 -16
- package/dist/server/src/utils/sort-utils.d.ts.map +1 -1
- package/dist/server/src/utils/sort-utils.js +0 -42
- package/dist/server/src/utils/transports/stdio-transport.js +1 -1
- package/dist/server/src/utils/transports/streamable-http-transport.js +1 -1
- package/dist/server/src/utils/transports/transport-factory.d.ts.map +1 -1
- package/dist/server/src/utils/transports/transport-factory.js +26 -3
- package/dist/server/tests/contract/mcp-protocol/initialize.test.js +1 -1
- package/dist/server/tests/contract/mcp-protocol/tools-call.test.js +1 -1
- package/dist/server/tests/contract/mcp-protocol/tools-list.test.js +1 -1
- package/dist/server/tests/integration/gateway/fault-tolerance.test.js +1 -1
- package/dist/server/tests/integration/gateway/mcp-connection.test.js +1 -1
- package/dist/server/tests/types/logger-test-helpers.d.ts +1 -1
- package/dist/server/tests/types/logger-test-helpers.d.ts.map +1 -1
- package/dist/server/tests/unit/api/search.test.d.ts +2 -0
- package/dist/server/tests/unit/api/search.test.d.ts.map +1 -0
- package/dist/server/tests/unit/api/search.test.js +61 -0
- package/dist/server/tests/unit/config/config-migrator.test.js +45 -105
- package/dist/server/tests/unit/config/config-saver.test.js +1 -1
- package/dist/server/tests/unit/server/runner.test.js +5 -6
- package/dist/server/tests/unit/services/gateway-logging.test.js +1 -1
- package/dist/server/tests/unit/services/hub-manager-service.test.js +4 -5
- package/dist/server/tests/unit/services/hub-tools.service.test.js +80 -3
- package/dist/server/tests/unit/utils/log-rotator.test.js +1 -15
- package/dist/server/tests/unit/utils/logger.test.js +1 -23
- package/dist/server/tests/unit/utils/sort-utils.test.js +1 -92
- package/package.json +1 -3
- package/dist/client/assets/ResourceDetailView-B8Qo1_jK.js +0 -1
- package/dist/client/assets/ResourcesView-B12FzUdo.js +0 -1
- package/dist/client/assets/ResourcesView-Cc8RHtia.css +0 -1
- package/dist/client/assets/ServerDetail-Bz5_9yOY.js +0 -2
- package/dist/client/assets/ToolCallDialog-BhdPX-Kf.css +0 -1
- package/dist/client/assets/ToolCallDialog-DYEdhnCk.js +0 -1
- package/dist/client/assets/ToolsView-BreAl-yn.js +0 -1
- package/dist/client/assets/ToolsView-BxgXvPC3.css +0 -1
- package/dist/client/assets/index-kC4mf0Vo.js +0 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.test.d.ts","sourceRoot":"","sources":["../../../../../tests/unit/api/search.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { filterByAggregatedTools } from '../../../src/api/web/search.js';
|
|
3
|
+
function mockConfig(aggregatedTools = []) {
|
|
4
|
+
return {
|
|
5
|
+
template: {
|
|
6
|
+
type: 'stdio',
|
|
7
|
+
command: 'test',
|
|
8
|
+
args: [],
|
|
9
|
+
env: {},
|
|
10
|
+
headers: {},
|
|
11
|
+
aggregatedTools,
|
|
12
|
+
timeout: 30000
|
|
13
|
+
},
|
|
14
|
+
instances: [],
|
|
15
|
+
tagDefinitions: []
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function mockTool(name, serverName = 'test-server') {
|
|
19
|
+
return { name, description: `Tool ${name}`, serverName };
|
|
20
|
+
}
|
|
21
|
+
describe('filterByAggregatedTools', () => {
|
|
22
|
+
it('should skip tools when aggregatedTools is empty', () => {
|
|
23
|
+
const tools = [mockTool('tool1'), mockTool('tool2')];
|
|
24
|
+
const getConfig = () => mockConfig([]);
|
|
25
|
+
const result = filterByAggregatedTools(tools, getConfig);
|
|
26
|
+
expect(result).toHaveLength(0);
|
|
27
|
+
});
|
|
28
|
+
it('should only include tools in aggregatedTools list', () => {
|
|
29
|
+
const tools = [mockTool('tool1'), mockTool('tool2'), mockTool('tool3')];
|
|
30
|
+
const getConfig = () => mockConfig(['tool1', 'tool3']);
|
|
31
|
+
const result = filterByAggregatedTools(tools, getConfig);
|
|
32
|
+
expect(result).toHaveLength(2);
|
|
33
|
+
expect(result.map((t) => t.name)).toEqual(['tool1', 'tool3']);
|
|
34
|
+
});
|
|
35
|
+
it('should skip tools when server config not found', () => {
|
|
36
|
+
const tools = [mockTool('tool1')];
|
|
37
|
+
const getConfig = () => undefined;
|
|
38
|
+
const result = filterByAggregatedTools(tools, getConfig);
|
|
39
|
+
expect(result).toHaveLength(0);
|
|
40
|
+
});
|
|
41
|
+
it('should filter tools from multiple servers independently', () => {
|
|
42
|
+
const tools = [
|
|
43
|
+
mockTool('a', 'server-a'),
|
|
44
|
+
mockTool('b', 'server-a'),
|
|
45
|
+
mockTool('x', 'server-b'),
|
|
46
|
+
mockTool('y', 'server-b')
|
|
47
|
+
];
|
|
48
|
+
const configs = {
|
|
49
|
+
'server-a': mockConfig(['a']),
|
|
50
|
+
'server-b': mockConfig([])
|
|
51
|
+
};
|
|
52
|
+
const getConfig = (name) => configs[name];
|
|
53
|
+
const result = filterByAggregatedTools(tools, getConfig);
|
|
54
|
+
expect(result).toHaveLength(1);
|
|
55
|
+
expect(result[0].name).toBe('a');
|
|
56
|
+
});
|
|
57
|
+
it('should return empty array when no tools provided', () => {
|
|
58
|
+
const result = filterByAggregatedTools([], () => mockConfig(['tool1']));
|
|
59
|
+
expect(result).toHaveLength(0);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import os from 'node:os';
|
|
5
|
-
import {
|
|
5
|
+
import { migrateConfig, resolveInstanceConfig } from '../../../src/config/config-migrator.js';
|
|
6
6
|
import { SystemConfigSchema, isLegacyV1Config } from '../../../src/config/config.schema.js';
|
|
7
7
|
describe('Config Migrator', () => {
|
|
8
8
|
let tempDir;
|
|
@@ -85,59 +85,15 @@ describe('Config Migrator', () => {
|
|
|
85
85
|
const config = createLegacyV1Config();
|
|
86
86
|
fs.writeFileSync(testConfigPath, JSON.stringify(config, null, 2));
|
|
87
87
|
};
|
|
88
|
-
describe('
|
|
89
|
-
it('should
|
|
90
|
-
const result = checkMigrationStatus(testConfigPath);
|
|
91
|
-
expect(result.exists).toBe(false);
|
|
92
|
-
expect(result.version).toBe('unknown');
|
|
93
|
-
expect(result.canMigrate).toBe(false);
|
|
94
|
-
});
|
|
95
|
-
it('should detect v1.0 configuration', () => {
|
|
96
|
-
writeLegacyV1Config();
|
|
97
|
-
const result = checkMigrationStatus(testConfigPath);
|
|
98
|
-
expect(result.exists).toBe(true);
|
|
99
|
-
expect(result.version).toBe('v1');
|
|
100
|
-
expect(result.canMigrate).toBe(true);
|
|
101
|
-
});
|
|
102
|
-
it('should detect v1.1 configuration', () => {
|
|
103
|
-
const v1_1Config = {
|
|
104
|
-
version: '1.1.0',
|
|
105
|
-
system: {
|
|
106
|
-
host: 'localhost',
|
|
107
|
-
port: 7788,
|
|
108
|
-
language: 'zh',
|
|
109
|
-
theme: 'system',
|
|
110
|
-
logging: {
|
|
111
|
-
level: 'info',
|
|
112
|
-
rotationAge: '7d',
|
|
113
|
-
jsonPretty: true,
|
|
114
|
-
mcpCommDebug: false,
|
|
115
|
-
apiDebug: false,
|
|
116
|
-
gatewayDebug: false
|
|
117
|
-
}
|
|
118
|
-
},
|
|
119
|
-
security: {
|
|
120
|
-
allowedNetworks: ['127.0.0.1'],
|
|
121
|
-
maxConcurrentConnections: 50,
|
|
122
|
-
connectionTimeout: 30000,
|
|
123
|
-
idleConnectionTimeout: 300000,
|
|
124
|
-
maxConnections: 50
|
|
125
|
-
},
|
|
126
|
-
servers: {},
|
|
127
|
-
tagDefinitions: []
|
|
128
|
-
};
|
|
129
|
-
fs.writeFileSync(testConfigPath, JSON.stringify(v1_1Config, null, 2));
|
|
130
|
-
const result = checkMigrationStatus(testConfigPath);
|
|
131
|
-
expect(result.exists).toBe(true);
|
|
132
|
-
expect(result.version).toBe('v1.1');
|
|
133
|
-
expect(result.canMigrate).toBe(false);
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
describe('dryRunMigration', () => {
|
|
137
|
-
it('should perform migration without modifying files', () => {
|
|
88
|
+
describe('migrateConfig (actual migration)', () => {
|
|
89
|
+
it('should perform dry run without modifying files', () => {
|
|
138
90
|
writeLegacyV1Config();
|
|
139
91
|
const originalContent = fs.readFileSync(testConfigPath, 'utf8');
|
|
140
|
-
const result =
|
|
92
|
+
const result = migrateConfig(testConfigPath, {
|
|
93
|
+
dryRun: true,
|
|
94
|
+
createBackup: false,
|
|
95
|
+
validateAfterMigration: true
|
|
96
|
+
});
|
|
141
97
|
expect(result.success).toBe(true);
|
|
142
98
|
expect(result.migratedConfig).toBeDefined();
|
|
143
99
|
expect(result.backupPath).toBeUndefined();
|
|
@@ -145,9 +101,13 @@ describe('Config Migrator', () => {
|
|
|
145
101
|
const currentContent = fs.readFileSync(testConfigPath, 'utf8');
|
|
146
102
|
expect(currentContent).toBe(originalContent);
|
|
147
103
|
});
|
|
148
|
-
it('should convert servers
|
|
104
|
+
it('should convert servers correctly (dry run)', () => {
|
|
149
105
|
writeLegacyV1Config();
|
|
150
|
-
const result =
|
|
106
|
+
const result = migrateConfig(testConfigPath, {
|
|
107
|
+
dryRun: true,
|
|
108
|
+
createBackup: false,
|
|
109
|
+
validateAfterMigration: true
|
|
110
|
+
});
|
|
151
111
|
expect(result.success).toBe(true);
|
|
152
112
|
expect(result.migratedConfig).toBeDefined();
|
|
153
113
|
const migrated = result.migratedConfig;
|
|
@@ -156,7 +116,6 @@ describe('Config Migrator', () => {
|
|
|
156
116
|
expect(Object.keys(migrated.servers)).toHaveLength(2);
|
|
157
117
|
expect(migrated.servers['test-server-1']).toBeDefined();
|
|
158
118
|
expect(migrated.servers['test-server-2']).toBeDefined();
|
|
159
|
-
// Check server 1
|
|
160
119
|
const server1 = migrated.servers['test-server-1'];
|
|
161
120
|
expect(server1.template.command).toBe('npx test-server-1');
|
|
162
121
|
expect(server1.template.args).toEqual(['--verbose']);
|
|
@@ -167,17 +126,19 @@ describe('Config Migrator', () => {
|
|
|
167
126
|
expect(server1.instances[0].id).toMatch(/test-server-1-[0-9a-f]{8}/);
|
|
168
127
|
expect(server1.instances[0].enabled).toBe(true);
|
|
169
128
|
});
|
|
170
|
-
it('should preserve system and security config', () => {
|
|
129
|
+
it('should preserve system and security config (dry run)', () => {
|
|
171
130
|
writeLegacyV1Config();
|
|
172
131
|
const v1Config = createLegacyV1Config();
|
|
173
|
-
const result =
|
|
132
|
+
const result = migrateConfig(testConfigPath, {
|
|
133
|
+
dryRun: true,
|
|
134
|
+
createBackup: false,
|
|
135
|
+
validateAfterMigration: true
|
|
136
|
+
});
|
|
174
137
|
expect(result.success).toBe(true);
|
|
175
138
|
const migrated = result.migratedConfig;
|
|
176
139
|
expect(migrated.system).toEqual(v1Config.system);
|
|
177
140
|
expect(migrated.security).toEqual(v1Config.security);
|
|
178
141
|
});
|
|
179
|
-
});
|
|
180
|
-
describe('migrateConfig (actual migration)', () => {
|
|
181
142
|
it('should perform actual migration and validate the result', () => {
|
|
182
143
|
writeLegacyV1Config();
|
|
183
144
|
const originalContent = fs.readFileSync(testConfigPath, 'utf8');
|
|
@@ -208,40 +169,15 @@ describe('Config Migrator', () => {
|
|
|
208
169
|
expect(validation.success).toBe(true);
|
|
209
170
|
});
|
|
210
171
|
});
|
|
211
|
-
describe('rollbackMigration', () => {
|
|
212
|
-
it('should rollback to v1.0 using backup', () => {
|
|
213
|
-
writeLegacyV1Config();
|
|
214
|
-
const originalContent = fs.readFileSync(testConfigPath, 'utf8');
|
|
215
|
-
// Perform migration first
|
|
216
|
-
const migrationResult = migrateConfig(testConfigPath, {
|
|
217
|
-
createBackup: true,
|
|
218
|
-
validateAfterMigration: true
|
|
219
|
-
});
|
|
220
|
-
expect(migrationResult.success).toBe(true);
|
|
221
|
-
expect(migrationResult.backupPath).toBeDefined();
|
|
222
|
-
// Verify file is v1.1
|
|
223
|
-
const migratedContent = fs.readFileSync(testConfigPath, 'utf8');
|
|
224
|
-
expect(migratedContent).not.toBe(originalContent);
|
|
225
|
-
// Rollback
|
|
226
|
-
const rollbackResult = rollbackMigration(testConfigPath, migrationResult.backupPath);
|
|
227
|
-
expect(rollbackResult.success).toBe(true);
|
|
228
|
-
// Verify file is back to original
|
|
229
|
-
const rolledBackContent = fs.readFileSync(testConfigPath, 'utf8');
|
|
230
|
-
expect(rolledBackContent).toBe(originalContent);
|
|
231
|
-
});
|
|
232
|
-
it('should fail rollback for non-existent backup', () => {
|
|
233
|
-
writeLegacyV1Config();
|
|
234
|
-
const nonExistentBackup = path.join(tempDir, 'non-existent-backup.json');
|
|
235
|
-
const result = rollbackMigration(testConfigPath, nonExistentBackup);
|
|
236
|
-
expect(result.success).toBe(false);
|
|
237
|
-
expect(result.error).toContain('not found');
|
|
238
|
-
});
|
|
239
|
-
});
|
|
240
172
|
describe('v1.1 Configuration Helpers', () => {
|
|
241
173
|
describe('resolveInstanceConfig', () => {
|
|
242
174
|
it('should resolve instance configuration by merging template and instance', () => {
|
|
243
175
|
writeLegacyV1Config();
|
|
244
|
-
const migrationResult =
|
|
176
|
+
const migrationResult = migrateConfig(testConfigPath, {
|
|
177
|
+
dryRun: true,
|
|
178
|
+
createBackup: false,
|
|
179
|
+
validateAfterMigration: true
|
|
180
|
+
});
|
|
245
181
|
expect(migrationResult.success).toBe(true);
|
|
246
182
|
const serverConfig = migrationResult.migratedConfig.servers['test-server-1'];
|
|
247
183
|
const resolved = resolveInstanceConfig(serverConfig);
|
|
@@ -255,7 +191,11 @@ describe('Config Migrator', () => {
|
|
|
255
191
|
});
|
|
256
192
|
it('should resolve specific instance by ID', () => {
|
|
257
193
|
writeLegacyV1Config();
|
|
258
|
-
const migrationResult =
|
|
194
|
+
const migrationResult = migrateConfig(testConfigPath, {
|
|
195
|
+
dryRun: true,
|
|
196
|
+
createBackup: false,
|
|
197
|
+
validateAfterMigration: true
|
|
198
|
+
});
|
|
259
199
|
expect(migrationResult.success).toBe(true);
|
|
260
200
|
const serverConfig = migrationResult.migratedConfig.servers['test-server-1'];
|
|
261
201
|
const instanceId = serverConfig.instances[0].id;
|
|
@@ -265,25 +205,17 @@ describe('Config Migrator', () => {
|
|
|
265
205
|
});
|
|
266
206
|
it('should return null for non-existent instance ID', () => {
|
|
267
207
|
writeLegacyV1Config();
|
|
268
|
-
const migrationResult =
|
|
208
|
+
const migrationResult = migrateConfig(testConfigPath, {
|
|
209
|
+
dryRun: true,
|
|
210
|
+
createBackup: false,
|
|
211
|
+
validateAfterMigration: true
|
|
212
|
+
});
|
|
269
213
|
expect(migrationResult.success).toBe(true);
|
|
270
214
|
const serverConfig = migrationResult.migratedConfig.servers['test-server-1'];
|
|
271
215
|
const resolved = resolveInstanceConfig(serverConfig, 'non-existent-id');
|
|
272
216
|
expect(resolved).toBeNull();
|
|
273
217
|
});
|
|
274
218
|
});
|
|
275
|
-
describe('getEnabledInstances', () => {
|
|
276
|
-
it('should return only enabled instances', () => {
|
|
277
|
-
writeLegacyV1Config();
|
|
278
|
-
const migrationResult = dryRunMigration(testConfigPath);
|
|
279
|
-
expect(migrationResult.success).toBe(true);
|
|
280
|
-
const serverConfig = migrationResult.migratedConfig.servers['test-server-1'];
|
|
281
|
-
const enabledInstances = getEnabledInstances(serverConfig);
|
|
282
|
-
expect(enabledInstances).toHaveLength(1);
|
|
283
|
-
expect(enabledInstances[0].instance.enabled).toBe(true);
|
|
284
|
-
expect(enabledInstances[0].resolved).not.toBeNull();
|
|
285
|
-
});
|
|
286
|
-
});
|
|
287
219
|
});
|
|
288
220
|
describe('Migration Edge Cases', () => {
|
|
289
221
|
it('should handle empty servers list', () => {
|
|
@@ -292,7 +224,11 @@ describe('Config Migrator', () => {
|
|
|
292
224
|
servers: {}
|
|
293
225
|
};
|
|
294
226
|
fs.writeFileSync(testConfigPath, JSON.stringify(emptyV1Config, null, 2));
|
|
295
|
-
const result =
|
|
227
|
+
const result = migrateConfig(testConfigPath, {
|
|
228
|
+
dryRun: true,
|
|
229
|
+
createBackup: false,
|
|
230
|
+
validateAfterMigration: true
|
|
231
|
+
});
|
|
296
232
|
expect(result.success).toBe(true);
|
|
297
233
|
expect(result.migratedConfig.servers).toEqual({});
|
|
298
234
|
});
|
|
@@ -310,7 +246,11 @@ describe('Config Migrator', () => {
|
|
|
310
246
|
}
|
|
311
247
|
};
|
|
312
248
|
fs.writeFileSync(testConfigPath, JSON.stringify(minimalV1Config, null, 2));
|
|
313
|
-
const result =
|
|
249
|
+
const result = migrateConfig(testConfigPath, {
|
|
250
|
+
dryRun: true,
|
|
251
|
+
createBackup: false,
|
|
252
|
+
validateAfterMigration: true
|
|
253
|
+
});
|
|
314
254
|
expect(result.success).toBe(true);
|
|
315
255
|
expect(result.migratedConfig.servers['minimal-server']).toBeDefined();
|
|
316
256
|
});
|
|
@@ -2,8 +2,8 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
|
2
2
|
import { runServer } from '../../../src/server/runner.js';
|
|
3
3
|
import { buildApp } from '../../../src/app.js';
|
|
4
4
|
import { configManager } from '../../../src/config/config-manager.js';
|
|
5
|
-
import { logger } from '../../../src/utils/logger.js';
|
|
6
|
-
import { mcpConnectionManager } from '../../../src/services/
|
|
5
|
+
import { logger } from '../../../src/utils/logger/index.js';
|
|
6
|
+
import { mcpConnectionManager } from '../../../src/services/connection/index.js';
|
|
7
7
|
import { PidManager } from '../../../src/pid/manager.js';
|
|
8
8
|
import { checkPort, checkPortWithExit } from '../../../src/utils/port-checker.js';
|
|
9
9
|
// Mock resolveInstanceConfig to return a valid resolved config
|
|
@@ -16,8 +16,7 @@ vi.mock('@config/config-migrator.js', () => ({
|
|
|
16
16
|
aggregatedTools: [],
|
|
17
17
|
tags: {},
|
|
18
18
|
enabled: true
|
|
19
|
-
}))
|
|
20
|
-
getEnabledInstances: vi.fn()
|
|
19
|
+
}))
|
|
21
20
|
}));
|
|
22
21
|
// Mock all dependencies
|
|
23
22
|
vi.mock('@src/app.js', () => ({
|
|
@@ -31,7 +30,7 @@ vi.mock('@config/config-manager.js', () => ({
|
|
|
31
30
|
addServerInstance: vi.fn()
|
|
32
31
|
}
|
|
33
32
|
}));
|
|
34
|
-
vi.mock('@utils/logger.js', () => ({
|
|
33
|
+
vi.mock('@utils/logger/index.js', () => ({
|
|
35
34
|
logger: {
|
|
36
35
|
info: vi.fn(),
|
|
37
36
|
error: vi.fn(),
|
|
@@ -43,7 +42,7 @@ vi.mock('@utils/logger.js', () => ({
|
|
|
43
42
|
SERVER: { module: 'Server' }
|
|
44
43
|
}
|
|
45
44
|
}));
|
|
46
|
-
vi.mock('@services/
|
|
45
|
+
vi.mock('@services/connection/index.js', () => ({
|
|
47
46
|
mcpConnectionManager: {
|
|
48
47
|
connect: vi.fn(() => Promise.resolve(true)),
|
|
49
48
|
disconnectAll: vi.fn()
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, test, expect } from 'vitest';
|
|
2
|
-
import { GatewayService } from '../../../src/services/gateway.
|
|
2
|
+
import { GatewayService } from '../../../src/services/gateway/index.js';
|
|
3
3
|
describe('GatewayService Logging Helpers', () => {
|
|
4
4
|
// Access private methods using type assertion for testing
|
|
5
5
|
const gateway = new GatewayService();
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
2
|
import { HubManagerService } from '../../../src/services/hub-manager.service.js';
|
|
3
|
-
import { mcpConnectionManager } from '../../../src/services/
|
|
3
|
+
import { mcpConnectionManager } from '../../../src/services/connection/index.js';
|
|
4
4
|
import { configManager } from '../../../src/config/config-manager.js';
|
|
5
5
|
// Mock resolveInstanceConfig to return a valid resolved config
|
|
6
6
|
vi.mock('@config/config-migrator.js', () => ({
|
|
7
|
-
resolveInstanceConfig: vi.fn()
|
|
8
|
-
getEnabledInstances: vi.fn()
|
|
7
|
+
resolveInstanceConfig: vi.fn()
|
|
9
8
|
}));
|
|
10
9
|
// Mock dependencies
|
|
11
10
|
vi.mock('@config/config-manager.js', () => ({
|
|
@@ -23,13 +22,13 @@ vi.mock('@config/config-manager.js', () => ({
|
|
|
23
22
|
removeServerInstance: vi.fn()
|
|
24
23
|
}
|
|
25
24
|
}));
|
|
26
|
-
vi.mock('@services/
|
|
25
|
+
vi.mock('@services/connection/index.js', () => ({
|
|
27
26
|
mcpConnectionManager: {
|
|
28
27
|
connect: vi.fn(),
|
|
29
28
|
disconnect: vi.fn(() => Promise.resolve())
|
|
30
29
|
}
|
|
31
30
|
}));
|
|
32
|
-
vi.mock('@utils/logger.js', () => ({
|
|
31
|
+
vi.mock('@utils/logger/index.js', () => ({
|
|
33
32
|
logger: {
|
|
34
33
|
info: vi.fn(),
|
|
35
34
|
error: vi.fn(),
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
2
|
import { HubToolsService } from '../../../src/services/hub-tools.service.js';
|
|
3
3
|
import { hubManager } from '../../../src/services/hub-manager.service.js';
|
|
4
|
-
import { mcpConnectionManager } from '../../../src/services/
|
|
4
|
+
import { mcpConnectionManager } from '../../../src/services/connection/index.js';
|
|
5
5
|
// Mock dependencies
|
|
6
6
|
vi.mock('@services/hub-manager.service.js');
|
|
7
|
-
vi.mock('@services/
|
|
7
|
+
vi.mock('@services/connection/index.js');
|
|
8
8
|
describe('HubToolsService', () => {
|
|
9
9
|
let hubToolsService;
|
|
10
10
|
beforeEach(() => {
|
|
@@ -320,6 +320,82 @@ describe('HubToolsService', () => {
|
|
|
320
320
|
});
|
|
321
321
|
expect(servers).not.toHaveProperty('Disconnected Server');
|
|
322
322
|
});
|
|
323
|
+
it('should include tag-match-unique servers with multiple connected instances', async () => {
|
|
324
|
+
// Arrange: 4-instance server with tag-match-unique strategy, all connected
|
|
325
|
+
const mockServers = [
|
|
326
|
+
{
|
|
327
|
+
name: 'multi-instance-server',
|
|
328
|
+
config: {
|
|
329
|
+
template: {
|
|
330
|
+
type: 'stdio',
|
|
331
|
+
command: 'test-command',
|
|
332
|
+
args: [],
|
|
333
|
+
env: {},
|
|
334
|
+
headers: {},
|
|
335
|
+
aggregatedTools: [],
|
|
336
|
+
timeout: 30000,
|
|
337
|
+
description: 'Multi-instance test server',
|
|
338
|
+
tags: {}
|
|
339
|
+
},
|
|
340
|
+
instances: [
|
|
341
|
+
{
|
|
342
|
+
id: 'inst-0',
|
|
343
|
+
index: 0,
|
|
344
|
+
enabled: false,
|
|
345
|
+
args: [],
|
|
346
|
+
env: {},
|
|
347
|
+
headers: {},
|
|
348
|
+
tags: { Env: 'dev' }
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
id: 'inst-1',
|
|
352
|
+
index: 1,
|
|
353
|
+
enabled: false,
|
|
354
|
+
args: [],
|
|
355
|
+
env: {},
|
|
356
|
+
headers: {},
|
|
357
|
+
tags: { Env: 'test' }
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
id: 'inst-2',
|
|
361
|
+
index: 2,
|
|
362
|
+
enabled: false,
|
|
363
|
+
args: [],
|
|
364
|
+
env: {},
|
|
365
|
+
headers: {},
|
|
366
|
+
tags: { Env: 'prod' }
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
id: 'inst-3',
|
|
370
|
+
index: 3,
|
|
371
|
+
enabled: false,
|
|
372
|
+
args: [],
|
|
373
|
+
env: {},
|
|
374
|
+
headers: {},
|
|
375
|
+
tags: { Env: 'staging' }
|
|
376
|
+
}
|
|
377
|
+
],
|
|
378
|
+
tagDefinitions: [],
|
|
379
|
+
instanceSelectionStrategy: 'tag-match-unique'
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
];
|
|
383
|
+
vi.mocked(hubManager.getAllServers).mockReturnValue(mockServers);
|
|
384
|
+
vi.mocked(hubManager.getServerInstancesByName).mockReturnValue(mockServers[0].config.instances);
|
|
385
|
+
vi.mocked(hubManager.getServerByName).mockReturnValue(mockServers[0].config);
|
|
386
|
+
vi.mocked(mcpConnectionManager.getConnectedIndexes).mockReturnValue([0, 1, 2, 3]);
|
|
387
|
+
vi.mocked(mcpConnectionManager.getStatus).mockReturnValue({
|
|
388
|
+
connected: true,
|
|
389
|
+
lastCheck: Date.now(),
|
|
390
|
+
toolsCount: 1,
|
|
391
|
+
resourcesCount: 0
|
|
392
|
+
});
|
|
393
|
+
// Act
|
|
394
|
+
const servers = await hubToolsService.listServers();
|
|
395
|
+
// Assert: tag-match-unique server with multiple connected instances must be included
|
|
396
|
+
expect(servers).toHaveProperty('multi-instance-server');
|
|
397
|
+
expect(servers['multi-instance-server']).toBe('Multi-instance test server');
|
|
398
|
+
});
|
|
323
399
|
});
|
|
324
400
|
describe('listToolsInServer', () => {
|
|
325
401
|
it('should return tool summaries from a specific server (without inputSchema)', async () => {
|
|
@@ -615,7 +691,8 @@ describe('HubToolsService', () => {
|
|
|
615
691
|
expect(systemToolNames).toContain('call_tool');
|
|
616
692
|
expect(systemToolNames).toContain('update_server_description');
|
|
617
693
|
expect(systemToolNames).toContain('list_tags');
|
|
618
|
-
expect(systemToolNames).
|
|
694
|
+
expect(systemToolNames).toContain('search_tools');
|
|
695
|
+
expect(systemToolNames).toHaveLength(7);
|
|
619
696
|
// Assert server tools - should have only name and description
|
|
620
697
|
expect(allTools['Server 1'].tools).toEqual(expectedToolSummariesServer1);
|
|
621
698
|
expect(allTools['Server 2'].tools).toEqual(expectedToolSummariesServer2);
|
|
@@ -79,21 +79,7 @@ describe('LogRotator', () => {
|
|
|
79
79
|
const latest = logRotator.getLatestLogFilePath();
|
|
80
80
|
expect(latest).toBeNull();
|
|
81
81
|
});
|
|
82
|
-
it('should
|
|
83
|
-
logRotator = new LogRotator(tempLogDir, 'mcp-hub', undefined, () => originalConfig);
|
|
84
|
-
// When no files exist, getCurrentLogFilePath will create a new path (but not the file)
|
|
85
|
-
const path1 = logRotator.getCurrentLogFilePath();
|
|
86
|
-
expect(path.basename(path1)).toMatch(/^mcp-hub\.\d{8}_\d{9}\.log$/);
|
|
87
|
-
// Actually create the file on disk
|
|
88
|
-
fs.writeFileSync(path1, 'test content 1');
|
|
89
|
-
// Create an older file
|
|
90
|
-
const oldFile = path.join(tempLogDir, 'mcp-hub.20260301_100000000.log');
|
|
91
|
-
fs.writeFileSync(oldFile, 'test content 2');
|
|
92
|
-
// When files exist, getCurrentLogFilePath should return the latest one
|
|
93
|
-
const path2 = logRotator.getCurrentLogFilePath();
|
|
94
|
-
expect(path2).toBe(path1); // Should return the newer one we just created
|
|
95
|
-
});
|
|
96
|
-
it('should get current log file path with custom base name', () => {
|
|
82
|
+
it('should create new log file path with custom base name', () => {
|
|
97
83
|
logRotator = new LogRotator(tempLogDir, 'custom-log', undefined, () => originalConfig);
|
|
98
84
|
const currentPath = logRotator.createNewLogFilePath();
|
|
99
85
|
const basename = path.basename(currentPath);
|
|
@@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import os from 'node:os';
|
|
5
|
-
import { Logger
|
|
5
|
+
import { Logger } from '../../../src/utils/logger/index.js';
|
|
6
6
|
import { setJsonPrettyConfigGetter, setDevModeEnabled } from '../../../src/utils/json-utils.js';
|
|
7
7
|
import { createColoredLogMessage, createLogMessage, getCallerInfo, formatCallerInfo } from '../../../src/utils/logger/log-formatter.js';
|
|
8
8
|
describe('Logger', () => {
|
|
@@ -363,28 +363,6 @@ describe('Logger', () => {
|
|
|
363
363
|
expect(writeSpy).toHaveBeenCalledWith(expect.stringContaining('file line 3'));
|
|
364
364
|
vi.restoreAllMocks();
|
|
365
365
|
});
|
|
366
|
-
it('should handle logWithColor function with traceId and spanId', () => {
|
|
367
|
-
// Reset logger instance to avoid interference between tests
|
|
368
|
-
const consoleInfoSpy = vi.spyOn(console, 'info').mockImplementation(() => { });
|
|
369
|
-
logWithColor('colored message', 'plain message', {
|
|
370
|
-
pid: 123,
|
|
371
|
-
serverName: 'test-server',
|
|
372
|
-
traceId: '1234567890abcdef1234567890abcdef',
|
|
373
|
-
spanId: 'abcdef1234567890'
|
|
374
|
-
});
|
|
375
|
-
expect(consoleInfoSpy).toHaveBeenCalledWith(expect.stringContaining('[TID:1234567890abcdef1234567890abcdef]'));
|
|
376
|
-
expect(consoleInfoSpy).toHaveBeenCalledWith(expect.stringContaining('[SID:abcdef1234567890]'));
|
|
377
|
-
// Restore console methods
|
|
378
|
-
consoleInfoSpy.mockRestore();
|
|
379
|
-
});
|
|
380
|
-
it('should handle logWithColor function without context', () => {
|
|
381
|
-
// Reset logger instance to avoid interference between tests
|
|
382
|
-
const consoleInfoSpy = vi.spyOn(console, 'info').mockImplementation(() => { });
|
|
383
|
-
logWithColor('colored message', 'plain message');
|
|
384
|
-
expect(consoleInfoSpy).toHaveBeenCalled();
|
|
385
|
-
// Restore console methods
|
|
386
|
-
consoleInfoSpy.mockRestore();
|
|
387
|
-
});
|
|
388
366
|
describe('dev log rotation', () => {
|
|
389
367
|
it('should create devLogRotator when enableDevLog is called', () => {
|
|
390
368
|
// Mock fs modules to avoid file system operations
|
|
@@ -1,50 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import { sortObjectKeysCaseInsensitive, sortServerConfigEnvHeaders } from '../../../src/utils/sort-utils.js';
|
|
3
3
|
describe('sort-utils', () => {
|
|
4
|
-
describe('sortObjectKeys', () => {
|
|
5
|
-
it('should sort object keys alphabetically using localeCompare', () => {
|
|
6
|
-
const obj = {
|
|
7
|
-
zebra: 'value3',
|
|
8
|
-
apple: 'value1',
|
|
9
|
-
banana: 'value2'
|
|
10
|
-
};
|
|
11
|
-
const result = sortObjectKeys(obj);
|
|
12
|
-
expect(Object.keys(result)).toEqual(['apple', 'banana', 'zebra']);
|
|
13
|
-
expect(result).toEqual({
|
|
14
|
-
apple: 'value1',
|
|
15
|
-
banana: 'value2',
|
|
16
|
-
zebra: 'value3'
|
|
17
|
-
});
|
|
18
|
-
});
|
|
19
|
-
it('should return the same object if input is null or undefined', () => {
|
|
20
|
-
expect(sortObjectKeys(null)).toBe(null);
|
|
21
|
-
expect(sortObjectKeys(undefined)).toBe(undefined);
|
|
22
|
-
});
|
|
23
|
-
it('should return the same object if input is an array', () => {
|
|
24
|
-
const arr = [3, 1, 2];
|
|
25
|
-
expect(sortObjectKeys(arr)).toBe(arr);
|
|
26
|
-
});
|
|
27
|
-
it('should return the same object if input is not an object', () => {
|
|
28
|
-
expect(sortObjectKeys('string')).toBe('string');
|
|
29
|
-
expect(sortObjectKeys(42)).toBe(42);
|
|
30
|
-
expect(sortObjectKeys(true)).toBe(true);
|
|
31
|
-
});
|
|
32
|
-
it('should preserve the original object (return a new object)', () => {
|
|
33
|
-
const obj = {
|
|
34
|
-
zebra: 'value3',
|
|
35
|
-
apple: 'value1'
|
|
36
|
-
};
|
|
37
|
-
const result = sortObjectKeys(obj);
|
|
38
|
-
expect(result).not.toBe(obj);
|
|
39
|
-
expect(obj).toEqual({
|
|
40
|
-
zebra: 'value3',
|
|
41
|
-
apple: 'value1'
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
it('should handle empty object', () => {
|
|
45
|
-
expect(sortObjectKeys({})).toEqual({});
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
4
|
describe('sortObjectKeysCaseInsensitive', () => {
|
|
49
5
|
it('should sort object keys alphabetically, case-insensitive', () => {
|
|
50
6
|
const obj = {
|
|
@@ -77,53 +33,6 @@ describe('sort-utils', () => {
|
|
|
77
33
|
expect(sortObjectKeysCaseInsensitive(undefined)).toBe(undefined);
|
|
78
34
|
});
|
|
79
35
|
});
|
|
80
|
-
describe('sortObjectKeysDeep', () => {
|
|
81
|
-
it('should recursively sort all object keys in a nested structure', () => {
|
|
82
|
-
const obj = {
|
|
83
|
-
zebra: {
|
|
84
|
-
delta: 'value4',
|
|
85
|
-
alpha: 'value1'
|
|
86
|
-
},
|
|
87
|
-
apple: {
|
|
88
|
-
charlie: 'value3',
|
|
89
|
-
bravo: 'value2'
|
|
90
|
-
}
|
|
91
|
-
};
|
|
92
|
-
const result = sortObjectKeysDeep(obj);
|
|
93
|
-
expect(Object.keys(result)).toEqual(['apple', 'zebra']);
|
|
94
|
-
expect(Object.keys(result.apple)).toEqual(['bravo', 'charlie']);
|
|
95
|
-
expect(Object.keys(result.zebra)).toEqual(['alpha', 'delta']);
|
|
96
|
-
});
|
|
97
|
-
it('should handle arrays and recursively sort objects within arrays', () => {
|
|
98
|
-
const obj = {
|
|
99
|
-
items: [
|
|
100
|
-
{ zebra: 'value3', apple: 'value1' },
|
|
101
|
-
{ delta: 'value4', alpha: 'value1' }
|
|
102
|
-
]
|
|
103
|
-
};
|
|
104
|
-
const result = sortObjectKeysDeep(obj);
|
|
105
|
-
expect(Array.isArray(result.items)).toBe(true);
|
|
106
|
-
expect(Object.keys(result.items[0])).toEqual(['apple', 'zebra']);
|
|
107
|
-
expect(Object.keys(result.items[1])).toEqual(['alpha', 'delta']);
|
|
108
|
-
});
|
|
109
|
-
it('should handle mixed types in nested structure', () => {
|
|
110
|
-
const obj = {
|
|
111
|
-
number: 42,
|
|
112
|
-
string: 'hello',
|
|
113
|
-
boolean: true,
|
|
114
|
-
array: [1, 2, 3],
|
|
115
|
-
object: {
|
|
116
|
-
nested: {
|
|
117
|
-
z: 'last',
|
|
118
|
-
a: 'first'
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
};
|
|
122
|
-
const result = sortObjectKeysDeep(obj);
|
|
123
|
-
expect(Object.keys(result)).toEqual(['array', 'boolean', 'number', 'object', 'string']);
|
|
124
|
-
expect(Object.keys(result.object.nested)).toEqual(['a', 'z']);
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
36
|
describe('sortServerConfigEnvHeaders', () => {
|
|
128
37
|
it('should sort env and headers objects in a server configuration', () => {
|
|
129
38
|
const config = {
|