@pikku/inspector 0.9.6-next.0 → 0.10.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.
Files changed (84) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/add/add-channel.d.ts +5 -1
  3. package/dist/add/add-channel.js +51 -32
  4. package/dist/add/add-cli.d.ts +4 -0
  5. package/dist/add/add-cli.js +128 -23
  6. package/dist/add/add-file-extends-core-type.js +3 -2
  7. package/dist/add/add-file-with-factory.d.ts +2 -2
  8. package/dist/add/add-file-with-factory.js +34 -1
  9. package/dist/add/add-functions.js +52 -5
  10. package/dist/add/add-http-route.js +19 -12
  11. package/dist/add/add-mcp-prompt.js +20 -13
  12. package/dist/add/add-mcp-resource.js +24 -14
  13. package/dist/add/add-mcp-tool.js +23 -13
  14. package/dist/add/add-middleware.js +51 -12
  15. package/dist/add/add-permission.d.ts +1 -2
  16. package/dist/add/add-permission.js +275 -19
  17. package/dist/add/add-queue-worker.js +10 -12
  18. package/dist/add/add-schedule.js +9 -10
  19. package/dist/error-codes.d.ts +35 -0
  20. package/dist/error-codes.js +40 -0
  21. package/dist/index.d.ts +4 -0
  22. package/dist/index.js +3 -0
  23. package/dist/inspector.js +20 -1
  24. package/dist/types.d.ts +31 -3
  25. package/dist/utils/ensure-function-metadata.d.ts +6 -0
  26. package/dist/utils/ensure-function-metadata.js +18 -0
  27. package/dist/utils/extract-function-name.d.ts +2 -2
  28. package/dist/utils/extract-function-name.js +13 -8
  29. package/dist/utils/filter-inspector-state.d.ts +6 -0
  30. package/dist/utils/filter-inspector-state.js +382 -0
  31. package/dist/utils/filter-utils.d.ts +10 -0
  32. package/dist/utils/filter-utils.js +66 -2
  33. package/dist/utils/find-root-dir.d.ts +23 -0
  34. package/dist/utils/find-root-dir.js +55 -0
  35. package/dist/utils/get-files-and-methods.d.ts +2 -1
  36. package/dist/utils/get-files-and-methods.js +2 -1
  37. package/dist/utils/get-property-value.d.ts +9 -0
  38. package/dist/utils/get-property-value.js +20 -0
  39. package/dist/utils/middleware.d.ts +1 -1
  40. package/dist/utils/middleware.js +7 -7
  41. package/dist/utils/permissions.d.ts +43 -0
  42. package/dist/utils/permissions.js +178 -0
  43. package/dist/utils/post-process.d.ts +16 -0
  44. package/dist/utils/post-process.js +132 -0
  45. package/dist/utils/serialize-inspector-state.d.ts +179 -0
  46. package/dist/utils/serialize-inspector-state.js +170 -0
  47. package/dist/visit.js +3 -2
  48. package/package.json +4 -4
  49. package/src/add/add-channel.ts +92 -40
  50. package/src/add/add-cli.ts +188 -29
  51. package/src/add/add-file-extends-core-type.ts +5 -2
  52. package/src/add/add-file-with-factory.ts +45 -2
  53. package/src/add/add-functions.ts +60 -5
  54. package/src/add/add-http-route.ts +46 -21
  55. package/src/add/add-mcp-prompt.ts +42 -21
  56. package/src/add/add-mcp-prompt.ts.tmp +0 -0
  57. package/src/add/add-mcp-resource.ts +50 -24
  58. package/src/add/add-mcp-resource.ts.tmp +0 -0
  59. package/src/add/add-mcp-tool.ts +48 -21
  60. package/src/add/add-middleware.ts +74 -15
  61. package/src/add/add-permission.ts +364 -22
  62. package/src/add/add-queue-worker.ts +22 -25
  63. package/src/add/add-schedule.ts +19 -20
  64. package/src/error-codes.ts +43 -0
  65. package/src/index.ts +7 -0
  66. package/src/inspector.ts +22 -1
  67. package/src/types.ts +38 -3
  68. package/src/utils/ensure-function-metadata.ts +24 -0
  69. package/src/utils/extract-function-name.ts +20 -8
  70. package/src/utils/filter-inspector-state.test.ts +1433 -0
  71. package/src/utils/filter-inspector-state.ts +526 -0
  72. package/src/utils/filter-utils.test.ts +350 -1
  73. package/src/utils/filter-utils.ts +82 -2
  74. package/src/utils/find-root-dir.ts +68 -0
  75. package/src/utils/get-files-and-methods.ts +8 -0
  76. package/src/utils/get-property-value.ts +27 -0
  77. package/src/utils/middleware.ts +14 -7
  78. package/src/utils/permissions.test.ts +327 -0
  79. package/src/utils/permissions.ts +262 -0
  80. package/src/utils/post-process.ts +178 -0
  81. package/src/utils/serialize-inspector-state.ts +375 -0
  82. package/src/utils/test-data/inspector-state.json +1680 -0
  83. package/src/visit.ts +4 -2
  84. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,1433 @@
1
+ import { test, describe } from 'node:test'
2
+ import { strict as assert } from 'node:assert'
3
+ import { filterInspectorState } from './filter-inspector-state.js'
4
+ import { InspectorState, InspectorFilters } from '../types.js'
5
+ import {
6
+ deserializeInspectorState,
7
+ SerializableInspectorState,
8
+ } from './serialize-inspector-state.js'
9
+ import { readFileSync } from 'node:fs'
10
+ import { fileURLToPath } from 'node:url'
11
+ import { dirname, join } from 'node:path'
12
+
13
+ const __filename = fileURLToPath(import.meta.url)
14
+ const __dirname = dirname(__filename)
15
+
16
+ // Load the real inspector state from templates/functions
17
+ let realState: Omit<InspectorState, 'typesLookup'>
18
+ try {
19
+ const stateJson = readFileSync(
20
+ join(__dirname, 'test-data/inspector-state.json'),
21
+ 'utf-8'
22
+ )
23
+ const serialized = JSON.parse(stateJson) as SerializableInspectorState
24
+ realState = deserializeInspectorState(serialized)
25
+ } catch (e) {
26
+ console.error('Failed to load real inspector state:', e)
27
+ throw e
28
+ }
29
+
30
+ // Helper to create a minimal mock InspectorState for testing (kept for compatibility)
31
+ function createMockInspectorState(): Omit<InspectorState, 'typesLookup'> {
32
+ return {
33
+ rootDir: '/test/project',
34
+ singletonServicesTypeImportMap: new Map(),
35
+ sessionServicesTypeImportMap: new Map(),
36
+ userSessionTypeImportMap: new Map(),
37
+ configTypeImportMap: new Map(),
38
+ singletonServicesFactories: new Map(),
39
+ sessionServicesFactories: new Map(),
40
+ sessionServicesMeta: new Map(),
41
+ configFactories: new Map(),
42
+ filesAndMethods: {},
43
+ filesAndMethodsErrors: new Map(),
44
+ http: {
45
+ metaInputTypes: new Map(),
46
+ meta: {
47
+ get: {
48
+ '/api/users': {
49
+ pikkuFuncName: 'getUsers',
50
+ route: '/api/users',
51
+ method: 'GET',
52
+ tags: ['api', 'public'],
53
+ middleware: [],
54
+ permissions: [],
55
+ },
56
+ '/admin/settings': {
57
+ pikkuFuncName: 'getAdminSettings',
58
+ route: '/admin/settings',
59
+ method: 'GET',
60
+ tags: ['admin'],
61
+ middleware: [{ type: 'wire', name: 'authMiddleware' }],
62
+ permissions: [{ type: 'wire', name: 'adminPermission' }],
63
+ },
64
+ },
65
+ post: {
66
+ '/api/users': {
67
+ pikkuFuncName: 'createUser',
68
+ route: '/api/users',
69
+ method: 'POST',
70
+ tags: ['api'],
71
+ middleware: [],
72
+ permissions: [],
73
+ },
74
+ },
75
+ put: {},
76
+ patch: {},
77
+ delete: {},
78
+ head: {},
79
+ options: {},
80
+ },
81
+ files: new Set(['/test/project/src/api/users.ts']),
82
+ routeMiddleware: new Map(),
83
+ routePermissions: new Map(),
84
+ },
85
+ functions: {
86
+ typesMap: new Map(),
87
+ meta: {
88
+ getUsers: {
89
+ services: [],
90
+ optimized: false,
91
+ },
92
+ createUser: {
93
+ services: [],
94
+ optimized: false,
95
+ },
96
+ getAdminSettings: {
97
+ services: [],
98
+ optimized: false,
99
+ },
100
+ sendEmailWorker: {
101
+ services: [],
102
+ optimized: false,
103
+ },
104
+ dailyReport: {
105
+ services: [],
106
+ optimized: false,
107
+ },
108
+ mcpSearchTool: {
109
+ services: [],
110
+ optimized: false,
111
+ },
112
+ cliCommand: {
113
+ services: [],
114
+ optimized: false,
115
+ },
116
+ },
117
+ files: new Map([
118
+ [
119
+ 'getUsers',
120
+ { path: '/test/project/src/api/users.ts', exportedName: 'getUsers' },
121
+ ],
122
+ [
123
+ 'createUser',
124
+ {
125
+ path: '/test/project/src/api/users.ts',
126
+ exportedName: 'createUser',
127
+ },
128
+ ],
129
+ [
130
+ 'getAdminSettings',
131
+ {
132
+ path: '/test/project/src/admin/settings.ts',
133
+ exportedName: 'getAdminSettings',
134
+ },
135
+ ],
136
+ [
137
+ 'sendEmailWorker',
138
+ {
139
+ path: '/test/project/src/workers/email.ts',
140
+ exportedName: 'sendEmailWorker',
141
+ },
142
+ ],
143
+ [
144
+ 'dailyReport',
145
+ {
146
+ path: '/test/project/src/tasks/reports.ts',
147
+ exportedName: 'dailyReport',
148
+ },
149
+ ],
150
+ ]),
151
+ },
152
+ channels: {
153
+ meta: {
154
+ 'chat-channel': {
155
+ pikkuFuncName: 'handleChatMessage',
156
+ tags: ['realtime', 'public'],
157
+ middleware: [],
158
+ permissions: [],
159
+ },
160
+ 'admin-channel': {
161
+ pikkuFuncName: 'handleAdminMessage',
162
+ tags: ['realtime', 'admin'],
163
+ middleware: [{ type: 'wire', name: 'authMiddleware' }],
164
+ permissions: [],
165
+ },
166
+ },
167
+ files: new Set(['/test/project/src/channels/chat.ts']),
168
+ },
169
+ scheduledTasks: {
170
+ meta: {
171
+ 'daily-report': {
172
+ pikkuFuncName: 'dailyReport',
173
+ schedule: '0 0 * * *',
174
+ tags: ['cron', 'reports'],
175
+ middleware: [],
176
+ },
177
+ 'hourly-cleanup': {
178
+ pikkuFuncName: 'hourlyCleanup',
179
+ schedule: '0 * * * *',
180
+ tags: ['cron', 'maintenance'],
181
+ middleware: [],
182
+ },
183
+ },
184
+ files: new Set(['/test/project/src/tasks/reports.ts']),
185
+ },
186
+ queueWorkers: {
187
+ meta: {
188
+ 'email-worker': {
189
+ pikkuFuncName: 'sendEmailWorker',
190
+ queueName: 'email-queue',
191
+ tags: ['queue', 'email'],
192
+ middleware: [],
193
+ },
194
+ 'notification-worker': {
195
+ pikkuFuncName: 'sendNotificationWorker',
196
+ queueName: 'notification-queue',
197
+ tags: ['queue', 'notifications'],
198
+ middleware: [],
199
+ },
200
+ },
201
+ files: new Set(['/test/project/src/workers/email.ts']),
202
+ },
203
+ rpc: {
204
+ internalMeta: {},
205
+ internalFiles: new Map(),
206
+ exposedMeta: {},
207
+ exposedFiles: new Map(),
208
+ invokedFunctions: new Set(),
209
+ },
210
+ mcpEndpoints: {
211
+ toolsMeta: {
212
+ 'search-tool': {
213
+ name: 'search-tool',
214
+ description: 'Search tool',
215
+ pikkuFuncName: 'mcpSearchTool',
216
+ tags: ['mcp', 'search'],
217
+ middleware: [],
218
+ permissions: [],
219
+ } as any,
220
+ 'analyze-tool': {
221
+ name: 'analyze-tool',
222
+ description: 'Analyze tool',
223
+ pikkuFuncName: 'mcpAnalyzeTool',
224
+ tags: ['mcp', 'analytics'],
225
+ middleware: [],
226
+ permissions: [],
227
+ } as any,
228
+ },
229
+ resourcesMeta: {
230
+ 'docs-resource': {
231
+ title: 'Docs Resource',
232
+ description: 'Documentation resource',
233
+ uri: 'docs://resource',
234
+ pikkuFuncName: 'mcpDocsResource',
235
+ tags: ['mcp', 'docs'],
236
+ middleware: [],
237
+ permissions: [],
238
+ } as any,
239
+ },
240
+ promptsMeta: {
241
+ 'help-prompt': {
242
+ name: 'help-prompt',
243
+ description: 'Help prompt',
244
+ pikkuFuncName: 'mcpHelpPrompt',
245
+ tags: ['mcp', 'help'],
246
+ middleware: [],
247
+ permissions: [],
248
+ } as any,
249
+ },
250
+ files: new Set(['/test/project/src/mcp/tools.ts']),
251
+ },
252
+ cli: {
253
+ meta: {
254
+ programs: {
255
+ 'my-cli': {
256
+ commands: {
257
+ build: {
258
+ pikkuFuncName: 'cliCommand',
259
+ tags: ['cli', 'build'],
260
+ middleware: [],
261
+ positionals: [],
262
+ options: {},
263
+ } as any,
264
+ test: {
265
+ pikkuFuncName: 'cliTestCommand',
266
+ tags: ['cli', 'test'],
267
+ middleware: [],
268
+ positionals: [],
269
+ options: {},
270
+ } as any,
271
+ },
272
+ },
273
+ },
274
+ },
275
+ files: new Set(['/test/project/src/cli/commands.ts']),
276
+ },
277
+ middleware: {
278
+ meta: {},
279
+ tagMiddleware: new Map(),
280
+ },
281
+ permissions: {
282
+ meta: {},
283
+ tagPermissions: new Map(),
284
+ },
285
+ serviceAggregation: {
286
+ requiredServices: new Set(),
287
+ usedFunctions: new Set(),
288
+ usedMiddleware: new Set(),
289
+ usedPermissions: new Set(),
290
+ },
291
+ }
292
+ }
293
+
294
+ // Mock logger for testing
295
+ const mockLogger = {
296
+ info: () => {},
297
+ error: () => {},
298
+ warn: () => {},
299
+ debug: () => {},
300
+ critical: () => {},
301
+ hasCriticalErrors: () => false,
302
+ }
303
+
304
+ describe('filterInspectorState', () => {
305
+ describe('No filters - returns original state', () => {
306
+ test('should return original state when no filters provided', () => {
307
+ const state = createMockInspectorState()
308
+ const filters: InspectorFilters = {}
309
+
310
+ const result = filterInspectorState(state, filters, mockLogger)
311
+
312
+ assert.equal(result, state) // Should be the same reference
313
+ })
314
+
315
+ test('should return original state when all filter arrays are empty', () => {
316
+ const state = createMockInspectorState()
317
+ const filters: InspectorFilters = {
318
+ names: [],
319
+ tags: [],
320
+ types: [],
321
+ directories: [],
322
+ httpRoutes: [],
323
+ httpMethods: [],
324
+ }
325
+
326
+ const result = filterInspectorState(state, filters, mockLogger)
327
+
328
+ assert.equal(result, state)
329
+ })
330
+ })
331
+
332
+ describe('HTTP filtering', () => {
333
+ test('should filter HTTP routes by type', () => {
334
+ const state = createMockInspectorState()
335
+ const filters: InspectorFilters = {
336
+ types: ['channel'], // Only channels, not HTTP
337
+ }
338
+
339
+ const result = filterInspectorState(state, filters, mockLogger)
340
+
341
+ // HTTP routes should be empty
342
+ assert.equal(Object.keys(result.http.meta.get).length, 0)
343
+ assert.equal(Object.keys(result.http.meta.post).length, 0)
344
+
345
+ // Channels should still exist
346
+ assert.equal(Object.keys(result.channels.meta).length, 2)
347
+ })
348
+
349
+ test('should filter HTTP routes by tags', () => {
350
+ const state = createMockInspectorState()
351
+ const filters: InspectorFilters = {
352
+ types: ['http'],
353
+ tags: ['admin'],
354
+ }
355
+
356
+ const result = filterInspectorState(state, filters, mockLogger)
357
+
358
+ // Only admin route should remain
359
+ assert.equal(Object.keys(result.http.meta.get).length, 1)
360
+ assert.ok(result.http.meta.get['/admin/settings'])
361
+ assert.equal(Object.keys(result.http.meta.post).length, 0)
362
+ })
363
+
364
+ test('should filter HTTP routes by httpMethod', () => {
365
+ const state = createMockInspectorState()
366
+ const filters: InspectorFilters = {
367
+ httpMethods: ['GET'],
368
+ }
369
+
370
+ const result = filterInspectorState(state, filters, mockLogger)
371
+
372
+ // Only GET routes should remain
373
+ assert.equal(Object.keys(result.http.meta.get).length, 2)
374
+ assert.equal(Object.keys(result.http.meta.post).length, 0)
375
+ })
376
+
377
+ test('should filter HTTP routes by httpRoute pattern', () => {
378
+ const state = createMockInspectorState()
379
+ const filters: InspectorFilters = {
380
+ httpRoutes: ['/api/*'],
381
+ }
382
+
383
+ const result = filterInspectorState(state, filters, mockLogger)
384
+
385
+ // Only /api/* routes should remain
386
+ assert.ok(result.http.meta.get['/api/users'])
387
+ assert.ok(result.http.meta.post['/api/users'])
388
+ assert.ok(!result.http.meta.get['/admin/settings'])
389
+ })
390
+
391
+ test('should filter HTTP routes by directory', () => {
392
+ const state = createMockInspectorState()
393
+ const filters: InspectorFilters = {
394
+ types: ['http'],
395
+ directories: ['src/admin'],
396
+ }
397
+
398
+ const result = filterInspectorState(state, filters, mockLogger)
399
+
400
+ // Only admin routes should remain
401
+ assert.equal(Object.keys(result.http.meta.get).length, 1)
402
+ assert.ok(result.http.meta.get['/admin/settings'])
403
+ })
404
+
405
+ test('should filter HTTP routes by function name', () => {
406
+ const state = createMockInspectorState()
407
+ const filters: InspectorFilters = {
408
+ types: ['http'],
409
+ names: ['getUsers'],
410
+ }
411
+
412
+ const result = filterInspectorState(state, filters, mockLogger)
413
+
414
+ assert.equal(Object.keys(result.http.meta.get).length, 1)
415
+ assert.ok(result.http.meta.get['/api/users'])
416
+ assert.equal(result.http.meta.get['/api/users'].pikkuFuncName, 'getUsers')
417
+ })
418
+
419
+ test('should filter HTTP routes by name wildcard', () => {
420
+ const state = createMockInspectorState()
421
+ const filters: InspectorFilters = {
422
+ types: ['http'],
423
+ names: ['get*'],
424
+ }
425
+
426
+ const result = filterInspectorState(state, filters, mockLogger)
427
+
428
+ // Should match getUsers and getAdminSettings
429
+ assert.equal(Object.keys(result.http.meta.get).length, 2)
430
+ assert.equal(Object.keys(result.http.meta.post).length, 0)
431
+ })
432
+
433
+ test('should track middleware and permissions for filtered HTTP routes', () => {
434
+ const state = createMockInspectorState()
435
+ const filters: InspectorFilters = {
436
+ types: ['http'],
437
+ tags: ['admin'],
438
+ }
439
+
440
+ const result = filterInspectorState(state, filters, mockLogger)
441
+
442
+ // Check that service aggregation includes the middleware and permissions
443
+ assert.ok(result.serviceAggregation.usedMiddleware.has('authMiddleware'))
444
+ assert.ok(
445
+ result.serviceAggregation.usedPermissions.has('adminPermission')
446
+ )
447
+ assert.ok(result.serviceAggregation.usedFunctions.has('getAdminSettings'))
448
+ })
449
+ })
450
+
451
+ describe('Channel filtering', () => {
452
+ test('should filter channels by type', () => {
453
+ const state = createMockInspectorState()
454
+ const filters: InspectorFilters = {
455
+ types: ['channel'],
456
+ }
457
+
458
+ const result = filterInspectorState(state, filters, mockLogger)
459
+
460
+ // Channels should exist
461
+ assert.equal(Object.keys(result.channels.meta).length, 2)
462
+
463
+ // HTTP routes should be empty
464
+ assert.equal(Object.keys(result.http.meta.get).length, 0)
465
+ assert.equal(Object.keys(result.http.meta.post).length, 0)
466
+ })
467
+
468
+ test('should filter channels by tags', () => {
469
+ const state = createMockInspectorState()
470
+ const filters: InspectorFilters = {
471
+ types: ['channel'],
472
+ tags: ['admin'],
473
+ }
474
+
475
+ const result = filterInspectorState(state, filters, mockLogger)
476
+
477
+ assert.equal(Object.keys(result.channels.meta).length, 1)
478
+ assert.ok(result.channels.meta['admin-channel'])
479
+ })
480
+
481
+ test('should filter channels by name', () => {
482
+ const state = createMockInspectorState()
483
+ const filters: InspectorFilters = {
484
+ names: ['chat-channel'],
485
+ }
486
+
487
+ const result = filterInspectorState(state, filters, mockLogger)
488
+
489
+ assert.equal(Object.keys(result.channels.meta).length, 1)
490
+ assert.ok(result.channels.meta['chat-channel'])
491
+ })
492
+
493
+ test('should filter channels by name wildcard', () => {
494
+ const state = createMockInspectorState()
495
+ const filters: InspectorFilters = {
496
+ names: ['*-channel'],
497
+ }
498
+
499
+ const result = filterInspectorState(state, filters, mockLogger)
500
+
501
+ assert.equal(Object.keys(result.channels.meta).length, 2)
502
+ })
503
+
504
+ test('should track middleware for filtered channels', () => {
505
+ const state = createMockInspectorState()
506
+ const filters: InspectorFilters = {
507
+ types: ['channel'],
508
+ tags: ['admin'],
509
+ }
510
+
511
+ const result = filterInspectorState(state, filters, mockLogger)
512
+
513
+ assert.ok(result.serviceAggregation.usedMiddleware.has('authMiddleware'))
514
+ assert.ok(
515
+ result.serviceAggregation.usedFunctions.has('handleAdminMessage')
516
+ )
517
+ })
518
+ })
519
+
520
+ describe('Scheduled task filtering', () => {
521
+ test('should filter scheduled tasks by type', () => {
522
+ const state = createMockInspectorState()
523
+ const filters: InspectorFilters = {
524
+ types: ['scheduler'],
525
+ }
526
+
527
+ const result = filterInspectorState(state, filters, mockLogger)
528
+
529
+ assert.equal(Object.keys(result.scheduledTasks.meta).length, 2)
530
+
531
+ // Other types should be empty
532
+ assert.equal(Object.keys(result.http.meta.get).length, 0)
533
+ assert.equal(Object.keys(result.channels.meta).length, 0)
534
+ })
535
+
536
+ test('should filter scheduled tasks by tags', () => {
537
+ const state = createMockInspectorState()
538
+ const filters: InspectorFilters = {
539
+ types: ['scheduler'],
540
+ tags: ['reports'],
541
+ }
542
+
543
+ const result = filterInspectorState(state, filters, mockLogger)
544
+
545
+ assert.equal(Object.keys(result.scheduledTasks.meta).length, 1)
546
+ assert.ok(result.scheduledTasks.meta['daily-report'])
547
+ })
548
+
549
+ test('should filter scheduled tasks by name', () => {
550
+ const state = createMockInspectorState()
551
+ const filters: InspectorFilters = {
552
+ names: ['daily-report'],
553
+ }
554
+
555
+ const result = filterInspectorState(state, filters, mockLogger)
556
+
557
+ assert.equal(Object.keys(result.scheduledTasks.meta).length, 1)
558
+ assert.ok(result.scheduledTasks.meta['daily-report'])
559
+ })
560
+
561
+ test('should filter scheduled tasks by name wildcard', () => {
562
+ const state = createMockInspectorState()
563
+ const filters: InspectorFilters = {
564
+ names: ['*-cleanup'],
565
+ }
566
+
567
+ const result = filterInspectorState(state, filters, mockLogger)
568
+
569
+ assert.equal(Object.keys(result.scheduledTasks.meta).length, 1)
570
+ assert.ok(result.scheduledTasks.meta['hourly-cleanup'])
571
+ })
572
+
573
+ test('should track functions for filtered scheduled tasks', () => {
574
+ const state = createMockInspectorState()
575
+ const filters: InspectorFilters = {
576
+ types: ['scheduler'],
577
+ tags: ['reports'],
578
+ }
579
+
580
+ const result = filterInspectorState(state, filters, mockLogger)
581
+
582
+ assert.ok(result.serviceAggregation.usedFunctions.has('dailyReport'))
583
+ })
584
+ })
585
+
586
+ describe('Queue worker filtering', () => {
587
+ test('should filter queue workers by type', () => {
588
+ const state = createMockInspectorState()
589
+ const filters: InspectorFilters = {
590
+ types: ['queue'],
591
+ }
592
+
593
+ const result = filterInspectorState(state, filters, mockLogger)
594
+
595
+ assert.equal(Object.keys(result.queueWorkers.meta).length, 2)
596
+
597
+ // Other types should be empty
598
+ assert.equal(Object.keys(result.http.meta.get).length, 0)
599
+ assert.equal(Object.keys(result.channels.meta).length, 0)
600
+ })
601
+
602
+ test('should filter queue workers by tags', () => {
603
+ const state = createMockInspectorState()
604
+ const filters: InspectorFilters = {
605
+ types: ['queue'],
606
+ tags: ['email'],
607
+ }
608
+
609
+ const result = filterInspectorState(state, filters, mockLogger)
610
+
611
+ assert.equal(Object.keys(result.queueWorkers.meta).length, 1)
612
+ assert.ok(result.queueWorkers.meta['email-worker'])
613
+ })
614
+
615
+ test('should filter queue workers by name', () => {
616
+ const state = createMockInspectorState()
617
+ const filters: InspectorFilters = {
618
+ names: ['email-worker'],
619
+ }
620
+
621
+ const result = filterInspectorState(state, filters, mockLogger)
622
+
623
+ assert.equal(Object.keys(result.queueWorkers.meta).length, 1)
624
+ assert.ok(result.queueWorkers.meta['email-worker'])
625
+ })
626
+
627
+ test('should filter queue workers by name wildcard', () => {
628
+ const state = createMockInspectorState()
629
+ const filters: InspectorFilters = {
630
+ names: ['*-worker'],
631
+ }
632
+
633
+ const result = filterInspectorState(state, filters, mockLogger)
634
+
635
+ assert.equal(Object.keys(result.queueWorkers.meta).length, 2)
636
+ })
637
+
638
+ test('should track functions for filtered queue workers', () => {
639
+ const state = createMockInspectorState()
640
+ const filters: InspectorFilters = {
641
+ types: ['queue'],
642
+ tags: ['email'],
643
+ }
644
+
645
+ const result = filterInspectorState(state, filters, mockLogger)
646
+
647
+ assert.ok(result.serviceAggregation.usedFunctions.has('sendEmailWorker'))
648
+ })
649
+ })
650
+
651
+ describe('MCP endpoint filtering', () => {
652
+ test('should filter MCP tools by type', () => {
653
+ const state = createMockInspectorState()
654
+ const filters: InspectorFilters = {
655
+ types: ['mcp'],
656
+ }
657
+
658
+ const result = filterInspectorState(state, filters, mockLogger)
659
+
660
+ assert.equal(Object.keys(result.mcpEndpoints.toolsMeta).length, 2)
661
+ assert.equal(Object.keys(result.mcpEndpoints.resourcesMeta).length, 1)
662
+ assert.equal(Object.keys(result.mcpEndpoints.promptsMeta).length, 1)
663
+
664
+ // Other types should be empty
665
+ assert.equal(Object.keys(result.http.meta.get).length, 0)
666
+ })
667
+
668
+ test('should filter MCP endpoints by tags', () => {
669
+ const state = createMockInspectorState()
670
+ const filters: InspectorFilters = {
671
+ types: ['mcp'],
672
+ tags: ['search'],
673
+ }
674
+
675
+ const result = filterInspectorState(state, filters, mockLogger)
676
+
677
+ assert.equal(Object.keys(result.mcpEndpoints.toolsMeta).length, 1)
678
+ assert.ok(result.mcpEndpoints.toolsMeta['search-tool'])
679
+ assert.equal(Object.keys(result.mcpEndpoints.resourcesMeta).length, 0)
680
+ })
681
+
682
+ test('should filter MCP endpoints by name', () => {
683
+ const state = createMockInspectorState()
684
+ const filters: InspectorFilters = {
685
+ names: ['search-tool'],
686
+ }
687
+
688
+ const result = filterInspectorState(state, filters, mockLogger)
689
+
690
+ assert.equal(Object.keys(result.mcpEndpoints.toolsMeta).length, 1)
691
+ assert.ok(result.mcpEndpoints.toolsMeta['search-tool'])
692
+ })
693
+
694
+ test('should filter MCP endpoints by name wildcard', () => {
695
+ const state = createMockInspectorState()
696
+ const filters: InspectorFilters = {
697
+ names: ['*-tool'],
698
+ }
699
+
700
+ const result = filterInspectorState(state, filters, mockLogger)
701
+
702
+ assert.equal(Object.keys(result.mcpEndpoints.toolsMeta).length, 2)
703
+ assert.equal(Object.keys(result.mcpEndpoints.resourcesMeta).length, 0)
704
+ assert.equal(Object.keys(result.mcpEndpoints.promptsMeta).length, 0)
705
+ })
706
+
707
+ test('should track functions for filtered MCP endpoints', () => {
708
+ const state = createMockInspectorState()
709
+ const filters: InspectorFilters = {
710
+ types: ['mcp'],
711
+ tags: ['search'],
712
+ }
713
+
714
+ const result = filterInspectorState(state, filters, mockLogger)
715
+
716
+ assert.ok(result.serviceAggregation.usedFunctions.has('mcpSearchTool'))
717
+ })
718
+ })
719
+
720
+ describe('CLI filtering', () => {
721
+ test('should filter CLI commands by type', () => {
722
+ const state = createMockInspectorState()
723
+ const filters: InspectorFilters = {
724
+ types: ['cli'],
725
+ }
726
+
727
+ const result = filterInspectorState(state, filters, mockLogger)
728
+
729
+ assert.equal(
730
+ Object.keys(result.cli.meta.programs['my-cli'].commands).length,
731
+ 2
732
+ )
733
+
734
+ // Other types should be empty
735
+ assert.equal(Object.keys(result.http.meta.get).length, 0)
736
+ })
737
+
738
+ test('should filter CLI commands by tags', () => {
739
+ const state = createMockInspectorState()
740
+ const filters: InspectorFilters = {
741
+ types: ['cli'],
742
+ tags: ['build'],
743
+ }
744
+
745
+ const result = filterInspectorState(state, filters, mockLogger)
746
+
747
+ assert.equal(
748
+ Object.keys(result.cli.meta.programs['my-cli'].commands).length,
749
+ 1
750
+ )
751
+ assert.ok(result.cli.meta.programs['my-cli'].commands['build'])
752
+ })
753
+
754
+ test('should filter CLI commands by name', () => {
755
+ const state = createMockInspectorState()
756
+ const filters: InspectorFilters = {
757
+ names: ['build'],
758
+ }
759
+
760
+ const result = filterInspectorState(state, filters, mockLogger)
761
+
762
+ assert.equal(
763
+ Object.keys(result.cli.meta.programs['my-cli'].commands).length,
764
+ 1
765
+ )
766
+ assert.ok(result.cli.meta.programs['my-cli'].commands['build'])
767
+ })
768
+
769
+ test('should remove program if all commands are filtered out', () => {
770
+ const state = createMockInspectorState()
771
+ const filters: InspectorFilters = {
772
+ types: ['cli'],
773
+ tags: ['non-existent'],
774
+ }
775
+
776
+ const result = filterInspectorState(state, filters, mockLogger)
777
+
778
+ // Program should be removed
779
+ assert.equal(Object.keys(result.cli.meta.programs).length, 0)
780
+ })
781
+
782
+ test('should track functions for filtered CLI commands', () => {
783
+ const state = createMockInspectorState()
784
+ const filters: InspectorFilters = {
785
+ types: ['cli'],
786
+ tags: ['build'],
787
+ }
788
+
789
+ const result = filterInspectorState(state, filters, mockLogger)
790
+
791
+ assert.ok(result.serviceAggregation.usedFunctions.has('cliCommand'))
792
+ })
793
+ })
794
+
795
+ describe('Combined filtering', () => {
796
+ test('should filter multiple types at once', () => {
797
+ const state = createMockInspectorState()
798
+ const filters: InspectorFilters = {
799
+ types: ['http', 'channel'],
800
+ }
801
+
802
+ const result = filterInspectorState(state, filters, mockLogger)
803
+
804
+ // HTTP and channels should exist
805
+ assert.equal(Object.keys(result.http.meta.get).length, 2)
806
+ assert.equal(Object.keys(result.channels.meta).length, 2)
807
+
808
+ // Other types should be empty
809
+ assert.equal(Object.keys(result.scheduledTasks.meta).length, 0)
810
+ assert.equal(Object.keys(result.queueWorkers.meta).length, 0)
811
+ })
812
+
813
+ test('should filter by tags across all types', () => {
814
+ const state = createMockInspectorState()
815
+ const filters: InspectorFilters = {
816
+ tags: ['admin'],
817
+ }
818
+
819
+ const result = filterInspectorState(state, filters, mockLogger)
820
+
821
+ // Only admin-tagged items should remain
822
+ assert.equal(Object.keys(result.http.meta.get).length, 1)
823
+ assert.ok(result.http.meta.get['/admin/settings'])
824
+ assert.equal(Object.keys(result.channels.meta).length, 1)
825
+ assert.ok(result.channels.meta['admin-channel'])
826
+ assert.equal(Object.keys(result.scheduledTasks.meta).length, 0)
827
+ })
828
+
829
+ test('should handle complex combined filters', () => {
830
+ const state = createMockInspectorState()
831
+ const filters: InspectorFilters = {
832
+ types: ['http'],
833
+ tags: ['api'],
834
+ httpMethods: ['POST'],
835
+ }
836
+
837
+ const result = filterInspectorState(state, filters, mockLogger)
838
+
839
+ // Only POST /api/users should remain
840
+ assert.equal(Object.keys(result.http.meta.get).length, 0)
841
+ assert.equal(Object.keys(result.http.meta.post).length, 1)
842
+ assert.ok(result.http.meta.post['/api/users'])
843
+ })
844
+ })
845
+
846
+ describe('Service aggregation', () => {
847
+ test('should reset and recalculate service aggregation', () => {
848
+ const state = createMockInspectorState()
849
+
850
+ // Pre-populate with some old data
851
+ state.serviceAggregation.usedFunctions.add('oldFunction')
852
+ state.serviceAggregation.usedMiddleware.add('oldMiddleware')
853
+
854
+ const filters: InspectorFilters = {
855
+ types: ['http'],
856
+ tags: ['admin'],
857
+ }
858
+
859
+ const result = filterInspectorState(state, filters, mockLogger)
860
+
861
+ // Old data should be gone, new data should be present
862
+ assert.ok(!result.serviceAggregation.usedFunctions.has('oldFunction'))
863
+ assert.ok(!result.serviceAggregation.usedMiddleware.has('oldMiddleware'))
864
+ assert.ok(result.serviceAggregation.usedFunctions.has('getAdminSettings'))
865
+ assert.ok(result.serviceAggregation.usedMiddleware.has('authMiddleware'))
866
+ })
867
+
868
+ test('should track all used functions across different wiring types', () => {
869
+ const state = createMockInspectorState()
870
+ const filters: InspectorFilters = {
871
+ tags: ['admin'], // Filter for admin-tagged items across all types
872
+ }
873
+
874
+ const result = filterInspectorState(state, filters, mockLogger)
875
+
876
+ // Should include functions from HTTP and channels
877
+ assert.ok(result.serviceAggregation.usedFunctions.has('getAdminSettings'))
878
+ assert.ok(
879
+ result.serviceAggregation.usedFunctions.has('handleAdminMessage')
880
+ )
881
+ })
882
+ })
883
+
884
+ describe('State immutability', () => {
885
+ test('should not mutate original state when filtering', () => {
886
+ const state = createMockInspectorState()
887
+ const originalHttpCount = Object.keys(state.http.meta.get).length
888
+ const originalChannelCount = Object.keys(state.channels.meta).length
889
+
890
+ const filters: InspectorFilters = {
891
+ types: ['http'],
892
+ }
893
+
894
+ filterInspectorState(state, filters, mockLogger)
895
+
896
+ // Original state should not be modified
897
+ assert.equal(Object.keys(state.http.meta.get).length, originalHttpCount)
898
+ assert.equal(
899
+ Object.keys(state.channels.meta).length,
900
+ originalChannelCount
901
+ )
902
+ })
903
+
904
+ test('should create deep copies of metadata', () => {
905
+ const state = createMockInspectorState()
906
+ const filters: InspectorFilters = {
907
+ types: ['http'],
908
+ tags: ['api'],
909
+ }
910
+
911
+ const result = filterInspectorState(state, filters, mockLogger)
912
+
913
+ // Modify filtered state
914
+ delete result.http.meta.get['/api/users']
915
+
916
+ // Original should still have the route
917
+ assert.ok(state.http.meta.get['/api/users'])
918
+ })
919
+ })
920
+
921
+ describe('Edge cases', () => {
922
+ test('should handle empty state', () => {
923
+ const state = createMockInspectorState()
924
+ state.http.meta = {
925
+ get: {},
926
+ post: {},
927
+ put: {},
928
+ patch: {},
929
+ delete: {},
930
+ head: {},
931
+ options: {},
932
+ }
933
+ state.channels.meta = {}
934
+ state.scheduledTasks.meta = {}
935
+ state.queueWorkers.meta = {}
936
+
937
+ const filters: InspectorFilters = {
938
+ types: ['http'],
939
+ }
940
+
941
+ const result = filterInspectorState(state, filters, mockLogger)
942
+
943
+ assert.equal(Object.keys(result.http.meta.get).length, 0)
944
+ })
945
+
946
+ test('should handle filters that match nothing', () => {
947
+ const state = createMockInspectorState()
948
+ const filters: InspectorFilters = {
949
+ tags: ['non-existent-tag'],
950
+ }
951
+
952
+ const result = filterInspectorState(state, filters, mockLogger)
953
+
954
+ // Everything should be filtered out
955
+ assert.equal(Object.keys(result.http.meta.get).length, 0)
956
+ assert.equal(Object.keys(result.channels.meta).length, 0)
957
+ assert.equal(Object.keys(result.scheduledTasks.meta).length, 0)
958
+ assert.equal(Object.keys(result.queueWorkers.meta).length, 0)
959
+ })
960
+
961
+ test('should handle missing optional metadata fields', () => {
962
+ const state = createMockInspectorState()
963
+ // Remove tags from one route
964
+ delete state.http.meta.get['/api/users'].tags
965
+
966
+ const filters: InspectorFilters = {
967
+ tags: ['api'],
968
+ }
969
+
970
+ const result = filterInspectorState(state, filters, mockLogger)
971
+
972
+ // Route without tags should not match
973
+ assert.ok(!result.http.meta.get['/api/users'])
974
+ // Route with tags should match
975
+ assert.ok(result.http.meta.post['/api/users'])
976
+ })
977
+ })
978
+
979
+ describe('Real data tests (from templates/functions)', () => {
980
+ test('should have loaded real state correctly', () => {
981
+ assert.ok(realState)
982
+ assert.ok(realState.http)
983
+ assert.ok(realState.functions)
984
+ assert.ok(realState.channels)
985
+ assert.ok(realState.scheduledTasks)
986
+ assert.ok(realState.queueWorkers)
987
+ assert.ok(realState.mcpEndpoints)
988
+ assert.ok(realState.cli)
989
+ })
990
+
991
+ test('should filter HTTP routes by type in real data', () => {
992
+ // BEFORE: Check what we have in the real state
993
+ const beforeHttpCount = Object.values(
994
+ realState.http.meta as Record<string, any>
995
+ ).flatMap((m) => Object.keys(m)).length
996
+ const beforeChannelCount = Object.keys(realState.channels.meta).length
997
+ const beforeTaskCount = Object.keys(realState.scheduledTasks.meta).length
998
+ const beforeQueueCount = Object.keys(realState.queueWorkers.meta).length
999
+
1000
+ assert.ok(beforeHttpCount > 0, 'Real state should have HTTP routes')
1001
+ assert.ok(beforeChannelCount > 0, 'Real state should have channels')
1002
+ assert.ok(beforeTaskCount > 0, 'Real state should have scheduled tasks')
1003
+ assert.ok(beforeQueueCount > 0, 'Real state should have queue workers')
1004
+
1005
+ // FILTER: Apply HTTP type filter
1006
+ const filters: InspectorFilters = {
1007
+ types: ['http'],
1008
+ }
1009
+ const result = filterInspectorState(realState, filters, mockLogger)
1010
+
1011
+ // AFTER: Check filtered result
1012
+ const afterHttpCount = Object.values(
1013
+ result.http.meta as Record<string, any>
1014
+ ).flatMap((m) => Object.keys(m)).length
1015
+ const afterChannelCount = Object.keys(result.channels.meta).length
1016
+ const afterTaskCount = Object.keys(result.scheduledTasks.meta).length
1017
+ const afterQueueCount = Object.keys(result.queueWorkers.meta).length
1018
+
1019
+ // HTTP routes should remain
1020
+ assert.equal(
1021
+ afterHttpCount,
1022
+ beforeHttpCount,
1023
+ 'HTTP routes should be preserved'
1024
+ )
1025
+
1026
+ // Other types should be filtered out
1027
+ assert.equal(afterChannelCount, 0, 'Channels should be filtered out')
1028
+ assert.equal(afterTaskCount, 0, 'Scheduled tasks should be filtered out')
1029
+ assert.equal(afterQueueCount, 0, 'Queue workers should be filtered out')
1030
+ })
1031
+
1032
+ test('should filter channels by type in real data', () => {
1033
+ const filters: InspectorFilters = {
1034
+ types: ['channel'],
1035
+ }
1036
+
1037
+ const result = filterInspectorState(realState, filters, mockLogger)
1038
+
1039
+ // Should have channels
1040
+ assert.ok(
1041
+ Object.keys(result.channels.meta).length > 0,
1042
+ 'Should have channels'
1043
+ )
1044
+
1045
+ // Other types should be filtered out
1046
+ const httpRouteCount = Object.values(result.http.meta).flatMap((m) =>
1047
+ Object.keys(m)
1048
+ ).length
1049
+ assert.equal(httpRouteCount, 0)
1050
+ assert.equal(Object.keys(result.scheduledTasks.meta).length, 0)
1051
+ assert.equal(Object.keys(result.queueWorkers.meta).length, 0)
1052
+ })
1053
+
1054
+ test('should filter scheduler tasks by type in real data', () => {
1055
+ // BEFORE: Check what we have in the real state
1056
+ const beforeHttpCount = Object.values(
1057
+ realState.http.meta as Record<string, any>
1058
+ ).flatMap((m) => Object.keys(m)).length
1059
+ const beforeChannelCount = Object.keys(realState.channels.meta).length
1060
+ const beforeTaskCount = Object.keys(realState.scheduledTasks.meta).length
1061
+ const beforeQueueCount = Object.keys(realState.queueWorkers.meta).length
1062
+
1063
+ assert.ok(beforeHttpCount > 0, 'Real state should have HTTP routes')
1064
+ assert.ok(beforeChannelCount > 0, 'Real state should have channels')
1065
+ assert.ok(beforeTaskCount > 0, 'Real state should have scheduled tasks')
1066
+ assert.ok(beforeQueueCount > 0, 'Real state should have queue workers')
1067
+
1068
+ // FILTER: Apply scheduler type filter
1069
+ const filters: InspectorFilters = {
1070
+ types: ['scheduler'],
1071
+ }
1072
+ const result = filterInspectorState(realState, filters, mockLogger)
1073
+
1074
+ // AFTER: Check filtered result
1075
+ const afterHttpCount = Object.values(
1076
+ result.http.meta as Record<string, any>
1077
+ ).flatMap((m) => Object.keys(m)).length
1078
+ const afterChannelCount = Object.keys(result.channels.meta).length
1079
+ const afterTaskCount = Object.keys(result.scheduledTasks.meta).length
1080
+ const afterQueueCount = Object.keys(result.queueWorkers.meta).length
1081
+
1082
+ // Scheduled tasks should remain
1083
+ assert.equal(
1084
+ afterTaskCount,
1085
+ beforeTaskCount,
1086
+ 'Scheduled tasks should be preserved'
1087
+ )
1088
+
1089
+ // Other types should be filtered out
1090
+ assert.equal(afterHttpCount, 0, 'HTTP routes should be filtered out')
1091
+ assert.equal(afterChannelCount, 0, 'Channels should be filtered out')
1092
+ assert.equal(afterQueueCount, 0, 'Queue workers should be filtered out')
1093
+ })
1094
+
1095
+ test('should filter queue workers by type in real data', () => {
1096
+ // BEFORE: Check what we have in the real state
1097
+ const beforeHttpCount = Object.values(
1098
+ realState.http.meta as Record<string, any>
1099
+ ).flatMap((m) => Object.keys(m)).length
1100
+ const beforeChannelCount = Object.keys(realState.channels.meta).length
1101
+ const beforeTaskCount = Object.keys(realState.scheduledTasks.meta).length
1102
+ const beforeQueueCount = Object.keys(realState.queueWorkers.meta).length
1103
+
1104
+ assert.ok(beforeHttpCount > 0, 'Real state should have HTTP routes')
1105
+ assert.ok(beforeChannelCount > 0, 'Real state should have channels')
1106
+ assert.ok(beforeTaskCount > 0, 'Real state should have scheduled tasks')
1107
+ assert.ok(beforeQueueCount > 0, 'Real state should have queue workers')
1108
+
1109
+ // FILTER: Apply queue type filter
1110
+ const filters: InspectorFilters = {
1111
+ types: ['queue'],
1112
+ }
1113
+ const result = filterInspectorState(realState, filters, mockLogger)
1114
+
1115
+ // AFTER: Check filtered result
1116
+ const afterHttpCount = Object.values(
1117
+ result.http.meta as Record<string, any>
1118
+ ).flatMap((m) => Object.keys(m)).length
1119
+ const afterChannelCount = Object.keys(result.channels.meta).length
1120
+ const afterTaskCount = Object.keys(result.scheduledTasks.meta).length
1121
+ const afterQueueCount = Object.keys(result.queueWorkers.meta).length
1122
+
1123
+ // Queue workers should remain
1124
+ assert.equal(
1125
+ afterQueueCount,
1126
+ beforeQueueCount,
1127
+ 'Queue workers should be preserved'
1128
+ )
1129
+
1130
+ // Other types should be filtered out
1131
+ assert.equal(afterHttpCount, 0, 'HTTP routes should be filtered out')
1132
+ assert.equal(afterChannelCount, 0, 'Channels should be filtered out')
1133
+ assert.equal(afterTaskCount, 0, 'Scheduled tasks should be filtered out')
1134
+ })
1135
+
1136
+ test('should filter MCP endpoints by type in real data', () => {
1137
+ // BEFORE: Check what we have in the real state
1138
+ const beforeHttpCount = Object.values(
1139
+ realState.http.meta as Record<string, any>
1140
+ ).flatMap((m) => Object.keys(m)).length
1141
+ const beforeChannelCount = Object.keys(realState.channels.meta).length
1142
+ const beforeTaskCount = Object.keys(realState.scheduledTasks.meta).length
1143
+ const beforeQueueCount = Object.keys(realState.queueWorkers.meta).length
1144
+ const beforeMcpToolsCount = Object.keys(
1145
+ realState.mcpEndpoints.toolsMeta
1146
+ ).length
1147
+ const beforeMcpResourcesCount = Object.keys(
1148
+ realState.mcpEndpoints.resourcesMeta
1149
+ ).length
1150
+ const beforeMcpPromptsCount = Object.keys(
1151
+ realState.mcpEndpoints.promptsMeta
1152
+ ).length
1153
+
1154
+ assert.ok(beforeHttpCount > 0, 'Real state should have HTTP routes')
1155
+ assert.ok(beforeChannelCount > 0, 'Real state should have channels')
1156
+ assert.ok(beforeTaskCount > 0, 'Real state should have scheduled tasks')
1157
+ assert.ok(beforeQueueCount > 0, 'Real state should have queue workers')
1158
+ assert.ok(
1159
+ beforeMcpToolsCount + beforeMcpResourcesCount + beforeMcpPromptsCount >
1160
+ 0,
1161
+ 'Real state should have MCP endpoints'
1162
+ )
1163
+
1164
+ // FILTER: Apply MCP type filter
1165
+ const filters: InspectorFilters = {
1166
+ types: ['mcp'],
1167
+ }
1168
+ const result = filterInspectorState(realState, filters, mockLogger)
1169
+
1170
+ // AFTER: Check filtered result
1171
+ const afterHttpCount = Object.values(
1172
+ result.http.meta as Record<string, any>
1173
+ ).flatMap((m) => Object.keys(m)).length
1174
+ const afterChannelCount = Object.keys(result.channels.meta).length
1175
+ const afterTaskCount = Object.keys(result.scheduledTasks.meta).length
1176
+ const afterQueueCount = Object.keys(result.queueWorkers.meta).length
1177
+ const afterMcpToolsCount = Object.keys(
1178
+ result.mcpEndpoints.toolsMeta
1179
+ ).length
1180
+ const afterMcpResourcesCount = Object.keys(
1181
+ result.mcpEndpoints.resourcesMeta
1182
+ ).length
1183
+ const afterMcpPromptsCount = Object.keys(
1184
+ result.mcpEndpoints.promptsMeta
1185
+ ).length
1186
+
1187
+ // MCP endpoints should remain
1188
+ assert.equal(
1189
+ afterMcpToolsCount,
1190
+ beforeMcpToolsCount,
1191
+ 'MCP tools should be preserved'
1192
+ )
1193
+ assert.equal(
1194
+ afterMcpResourcesCount,
1195
+ beforeMcpResourcesCount,
1196
+ 'MCP resources should be preserved'
1197
+ )
1198
+ assert.equal(
1199
+ afterMcpPromptsCount,
1200
+ beforeMcpPromptsCount,
1201
+ 'MCP prompts should be preserved'
1202
+ )
1203
+
1204
+ // Other types should be filtered out
1205
+ assert.equal(afterHttpCount, 0, 'HTTP routes should be filtered out')
1206
+ assert.equal(afterChannelCount, 0, 'Channels should be filtered out')
1207
+ assert.equal(afterTaskCount, 0, 'Scheduled tasks should be filtered out')
1208
+ assert.equal(afterQueueCount, 0, 'Queue workers should be filtered out')
1209
+ })
1210
+
1211
+ test('should filter CLI programs by type in real data', () => {
1212
+ // BEFORE: Check what we have in the real state
1213
+ const beforeHttpCount = Object.values(
1214
+ realState.http.meta as Record<string, any>
1215
+ ).flatMap((m) => Object.keys(m)).length
1216
+ const beforeChannelCount = Object.keys(realState.channels.meta).length
1217
+ const beforeTaskCount = Object.keys(realState.scheduledTasks.meta).length
1218
+ const beforeQueueCount = Object.keys(realState.queueWorkers.meta).length
1219
+ const beforeCliProgramCount = Object.keys(
1220
+ realState.cli.meta.programs
1221
+ ).length
1222
+
1223
+ assert.ok(beforeHttpCount > 0, 'Real state should have HTTP routes')
1224
+ assert.ok(beforeChannelCount > 0, 'Real state should have channels')
1225
+ assert.ok(beforeTaskCount > 0, 'Real state should have scheduled tasks')
1226
+ assert.ok(beforeQueueCount > 0, 'Real state should have queue workers')
1227
+ assert.ok(
1228
+ beforeCliProgramCount > 0,
1229
+ 'Real state should have CLI programs'
1230
+ )
1231
+
1232
+ // FILTER: Apply CLI type filter
1233
+ const filters: InspectorFilters = {
1234
+ types: ['cli'],
1235
+ }
1236
+ const result = filterInspectorState(realState, filters, mockLogger)
1237
+
1238
+ // AFTER: Check filtered result
1239
+ const afterHttpCount = Object.values(
1240
+ result.http.meta as Record<string, any>
1241
+ ).flatMap((m) => Object.keys(m)).length
1242
+ const afterChannelCount = Object.keys(result.channels.meta).length
1243
+ const afterTaskCount = Object.keys(result.scheduledTasks.meta).length
1244
+ const afterQueueCount = Object.keys(result.queueWorkers.meta).length
1245
+ const afterCliProgramCount = Object.keys(result.cli.meta.programs).length
1246
+
1247
+ // CLI programs should remain
1248
+ assert.equal(
1249
+ afterCliProgramCount,
1250
+ beforeCliProgramCount,
1251
+ 'CLI programs should be preserved'
1252
+ )
1253
+
1254
+ // Other types should be filtered out
1255
+ assert.equal(afterHttpCount, 0, 'HTTP routes should be filtered out')
1256
+ assert.equal(afterChannelCount, 0, 'Channels should be filtered out')
1257
+ assert.equal(afterTaskCount, 0, 'Scheduled tasks should be filtered out')
1258
+ assert.equal(afterQueueCount, 0, 'Queue workers should be filtered out')
1259
+ })
1260
+
1261
+ test('should filter by HTTP method in real data', () => {
1262
+ // BEFORE: Check what HTTP methods exist
1263
+ const beforeGetCount = Object.keys(realState.http.meta.get || {}).length
1264
+ const beforePostCount = Object.keys(realState.http.meta.post || {}).length
1265
+ const beforePutCount = Object.keys(realState.http.meta.put || {}).length
1266
+ const beforeDeleteCount = Object.keys(
1267
+ realState.http.meta.delete || {}
1268
+ ).length
1269
+
1270
+ assert.ok(
1271
+ beforeGetCount + beforePostCount + beforePutCount + beforeDeleteCount >
1272
+ 0,
1273
+ 'Real state should have HTTP routes'
1274
+ )
1275
+
1276
+ // FILTER: Apply GET method filter
1277
+ const filters: InspectorFilters = {
1278
+ types: ['http'],
1279
+ httpMethods: ['GET'],
1280
+ }
1281
+ const result = filterInspectorState(realState, filters, mockLogger)
1282
+
1283
+ // AFTER: Check filtered result
1284
+ const afterGetCount = Object.keys(result.http.meta.get || {}).length
1285
+ const afterPostCount = Object.keys(result.http.meta.post || {}).length
1286
+ const afterPutCount = Object.keys(result.http.meta.put || {}).length
1287
+ const afterDeleteCount = Object.keys(result.http.meta.delete || {}).length
1288
+
1289
+ // GET routes should remain
1290
+ assert.equal(
1291
+ afterGetCount,
1292
+ beforeGetCount,
1293
+ 'GET routes should be preserved'
1294
+ )
1295
+
1296
+ // Other methods should be filtered out
1297
+ assert.equal(afterPostCount, 0, 'POST routes should be filtered out')
1298
+ assert.equal(afterPutCount, 0, 'PUT routes should be filtered out')
1299
+ assert.equal(afterDeleteCount, 0, 'DELETE routes should be filtered out')
1300
+ })
1301
+
1302
+ test('should filter by name pattern in real data', () => {
1303
+ // BEFORE: Count all endpoints
1304
+ const beforeHttpCount = Object.values(
1305
+ realState.http.meta as Record<string, any>
1306
+ ).flatMap((m) => Object.keys(m)).length
1307
+ const beforeChannelCount = Object.keys(realState.channels.meta).length
1308
+ const beforeTaskCount = Object.keys(realState.scheduledTasks.meta).length
1309
+ const beforeQueueCount = Object.keys(realState.queueWorkers.meta).length
1310
+ const beforeTotal =
1311
+ beforeHttpCount +
1312
+ beforeChannelCount +
1313
+ beforeTaskCount +
1314
+ beforeQueueCount
1315
+
1316
+ assert.ok(beforeTotal > 0, 'Real state should have endpoints')
1317
+
1318
+ // FILTER: Apply name pattern filter for functions with "http" in their name
1319
+ const filters: InspectorFilters = {
1320
+ names: ['*http*'],
1321
+ }
1322
+ const result = filterInspectorState(realState, filters, mockLogger)
1323
+
1324
+ // AFTER: Count all filtered endpoints
1325
+ const afterHttpCount = Object.values(
1326
+ result.http.meta as Record<string, any>
1327
+ ).flatMap((m) => Object.keys(m)).length
1328
+ const afterChannelCount = Object.keys(result.channels.meta).length
1329
+ const afterTaskCount = Object.keys(result.scheduledTasks.meta).length
1330
+ const afterQueueCount = Object.keys(result.queueWorkers.meta).length
1331
+ const afterTotal =
1332
+ afterHttpCount + afterChannelCount + afterTaskCount + afterQueueCount
1333
+
1334
+ // After filtering by name pattern, we should have fewer or equal items
1335
+ assert.ok(
1336
+ afterTotal <= beforeTotal,
1337
+ 'Filtered count should be less than or equal to original'
1338
+ )
1339
+ })
1340
+
1341
+ test('should not mutate real state when filtering', () => {
1342
+ const originalHttpCount = Object.values(
1343
+ realState.http.meta as Record<string, any>
1344
+ ).flatMap((m) => Object.keys(m)).length
1345
+ const originalChannelCount = Object.keys(realState.channels.meta).length
1346
+
1347
+ const filters: InspectorFilters = {
1348
+ types: ['http'],
1349
+ }
1350
+
1351
+ filterInspectorState(realState, filters, mockLogger)
1352
+
1353
+ // Original should not be modified
1354
+ const afterHttpCount = Object.values(
1355
+ realState.http.meta as Record<string, any>
1356
+ ).flatMap((m) => Object.keys(m)).length
1357
+ const afterChannelCount = Object.keys(realState.channels.meta).length
1358
+
1359
+ assert.equal(afterHttpCount, originalHttpCount)
1360
+ assert.equal(afterChannelCount, originalChannelCount)
1361
+ })
1362
+
1363
+ test('should aggregate services correctly with real data', () => {
1364
+ const filters: InspectorFilters = {
1365
+ types: ['http'],
1366
+ }
1367
+
1368
+ const result = filterInspectorState(realState, filters, mockLogger)
1369
+
1370
+ // Service aggregation should be recalculated
1371
+ assert.ok(result.serviceAggregation)
1372
+ assert.ok(result.serviceAggregation.usedFunctions instanceof Set)
1373
+ assert.ok(result.serviceAggregation.usedMiddleware instanceof Set)
1374
+ assert.ok(result.serviceAggregation.usedPermissions instanceof Set)
1375
+ assert.ok(result.serviceAggregation.requiredServices instanceof Set)
1376
+ })
1377
+
1378
+ test('should handle combined filters with real data', () => {
1379
+ // BEFORE: Check what we have
1380
+ const beforeGetCount = Object.keys(realState.http.meta.get || {}).length
1381
+ const beforePostCount = Object.keys(realState.http.meta.post || {}).length
1382
+ const beforePutCount = Object.keys(realState.http.meta.put || {}).length
1383
+ const beforeChannelCount = Object.keys(realState.channels.meta).length
1384
+ const beforeTaskCount = Object.keys(realState.scheduledTasks.meta).length
1385
+ const beforeQueueCount = Object.keys(realState.queueWorkers.meta).length
1386
+
1387
+ assert.ok(
1388
+ beforeGetCount + beforePostCount > 0,
1389
+ 'Real state should have GET/POST routes'
1390
+ )
1391
+ assert.ok(beforeChannelCount > 0, 'Real state should have channels')
1392
+
1393
+ // FILTER: Apply combined filters (HTTP and channels, GET/POST methods only)
1394
+ const filters: InspectorFilters = {
1395
+ types: ['http', 'channel'],
1396
+ httpMethods: ['GET', 'POST'],
1397
+ }
1398
+ const result = filterInspectorState(realState, filters, mockLogger)
1399
+
1400
+ // AFTER: Check filtered result
1401
+ const afterGetCount = Object.keys(result.http.meta.get || {}).length
1402
+ const afterPostCount = Object.keys(result.http.meta.post || {}).length
1403
+ const afterPutCount = Object.keys(result.http.meta.put || {}).length
1404
+ const afterChannelCount = Object.keys(result.channels.meta).length
1405
+ const afterTaskCount = Object.keys(result.scheduledTasks.meta).length
1406
+ const afterQueueCount = Object.keys(result.queueWorkers.meta).length
1407
+
1408
+ // GET and POST routes should be preserved
1409
+ assert.equal(
1410
+ afterGetCount,
1411
+ beforeGetCount,
1412
+ 'GET routes should be preserved'
1413
+ )
1414
+ assert.equal(
1415
+ afterPostCount,
1416
+ beforePostCount,
1417
+ 'POST routes should be preserved'
1418
+ )
1419
+
1420
+ // Channels should be preserved (no HTTP method filter applies)
1421
+ assert.equal(
1422
+ afterChannelCount,
1423
+ beforeChannelCount,
1424
+ 'Channels should be preserved'
1425
+ )
1426
+
1427
+ // Other methods/types should be filtered out
1428
+ assert.equal(afterPutCount, 0, 'PUT routes should be filtered out')
1429
+ assert.equal(afterTaskCount, 0, 'Scheduled tasks should be filtered out')
1430
+ assert.equal(afterQueueCount, 0, 'Queue workers should be filtered out')
1431
+ })
1432
+ })
1433
+ })