|
3 | 3 | */ |
4 | 4 |
|
5 | 5 | import * as path from 'path'; |
| 6 | +import * as fs from 'fs'; |
| 7 | +import * as os from 'os'; |
6 | 8 | import { parseProtoFile } from '../../src/postprocessing/parser'; |
7 | 9 | import { |
8 | 10 | isBuiltInType, |
9 | 11 | findReachableTypes, |
10 | 12 | filterMessages, |
11 | 13 | filterEnums, |
12 | | - extractRootsFromServices |
| 14 | + extractRootsFromServices, |
| 15 | + cleanupUnusedMessages, |
| 16 | + CleanupOptions |
13 | 17 | } from '../../src/postprocessing/CleanupUnusedMessages'; |
14 | 18 | import { ProtoMessage, ProtoEnum } from '../../src/postprocessing/types'; |
15 | 19 |
|
16 | 20 | const TEST_PROTO = path.join(__dirname, '../fixtures/proto/test.proto'); |
17 | 21 | const TEST_SERVICE_PROTO = path.join(__dirname, '../fixtures/proto/test_service.proto'); |
18 | 22 |
|
| 23 | +// Proto content for tests |
| 24 | +const PROTO_SERVICE_WITH_ROOTS = ` |
| 25 | +syntax = "proto3"; |
| 26 | +package test; |
| 27 | +
|
| 28 | +message SearchRequest { string query = 1; } |
| 29 | +message SearchResponse { string result = 1; } |
| 30 | +`; |
| 31 | + |
19 | 32 | describe('CleanupUnusedMessages', () => { |
20 | 33 | const parsed = parseProtoFile(TEST_PROTO); |
21 | 34 |
|
@@ -267,3 +280,119 @@ describe('isBuiltInType', () => { |
267 | 280 | expect(isBuiltInType('map<int32, CustomType>')).toBe(false); |
268 | 281 | }); |
269 | 282 | }); |
| 283 | + |
| 284 | +describe('cleanupUnusedMessages', () => { |
| 285 | + let tempDir: string; |
| 286 | + let outputPath: string; |
| 287 | + |
| 288 | + beforeEach(() => { |
| 289 | + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cleanup-test-')); |
| 290 | + outputPath = path.join(tempDir, 'output.proto'); |
| 291 | + }); |
| 292 | + |
| 293 | + afterEach(() => { |
| 294 | + if (fs.existsSync(tempDir)) { |
| 295 | + fs.rmSync(tempDir, { recursive: true }); |
| 296 | + } |
| 297 | + }); |
| 298 | + |
| 299 | + it('should cleanup unused messages with manual roots', () => { |
| 300 | + const opts: CleanupOptions = { |
| 301 | + input: TEST_PROTO, |
| 302 | + output: outputPath, |
| 303 | + roots: ['SearchRequest', 'SearchResponse'] |
| 304 | + }; |
| 305 | + |
| 306 | + const result = cleanupUnusedMessages(opts); |
| 307 | + |
| 308 | + expect(result.removedMessages).toBeGreaterThan(0); |
| 309 | + expect(fs.existsSync(outputPath)).toBe(true); |
| 310 | + |
| 311 | + const output = parseProtoFile(outputPath); |
| 312 | + const messageNames = output.messages.map(m => m.name); |
| 313 | + |
| 314 | + expect(messageNames).toContain('SearchRequest'); |
| 315 | + expect(messageNames).toContain('SearchResponse'); |
| 316 | + expect(messageNames).not.toContain('UnusedMessage'); |
| 317 | + }); |
| 318 | + |
| 319 | + it('should cleanup unused messages with service file', () => { |
| 320 | + const testService = path.join(tempDir, 'test_service.proto'); |
| 321 | + fs.writeFileSync(testService, PROTO_SERVICE_WITH_ROOTS); |
| 322 | + |
| 323 | + const opts: CleanupOptions = { |
| 324 | + input: TEST_PROTO, |
| 325 | + output: outputPath, |
| 326 | + service: testService |
| 327 | + }; |
| 328 | + |
| 329 | + const result = cleanupUnusedMessages(opts); |
| 330 | + |
| 331 | + expect(fs.existsSync(outputPath)).toBe(true); |
| 332 | + expect(result.removedMessages).toBeGreaterThanOrEqual(0); |
| 333 | + }); |
| 334 | + |
| 335 | + it('should throw error if input file not found', () => { |
| 336 | + const opts: CleanupOptions = { |
| 337 | + input: '/non/existent/path.proto', |
| 338 | + output: outputPath, |
| 339 | + roots: ['SomeMessage'] |
| 340 | + }; |
| 341 | + |
| 342 | + expect(() => cleanupUnusedMessages(opts)).toThrow('Input file not found'); |
| 343 | + }); |
| 344 | + |
| 345 | + it('should throw error if service file not found and no roots specified', () => { |
| 346 | + const opts: CleanupOptions = { |
| 347 | + input: TEST_PROTO, |
| 348 | + output: outputPath, |
| 349 | + service: '/non/existent/service.proto' |
| 350 | + }; |
| 351 | + |
| 352 | + expect(() => cleanupUnusedMessages(opts)).toThrow('Service file not found'); |
| 353 | + }); |
| 354 | + |
| 355 | + it('should throw error if root message not found', () => { |
| 356 | + const opts: CleanupOptions = { |
| 357 | + input: TEST_PROTO, |
| 358 | + output: outputPath, |
| 359 | + roots: ['NonExistentMessage'] |
| 360 | + }; |
| 361 | + |
| 362 | + expect(() => cleanupUnusedMessages(opts)).toThrow('Root message not found'); |
| 363 | + }); |
| 364 | + |
| 365 | + it('should write to input file if no output specified', () => { |
| 366 | + // Copy test proto to temp location |
| 367 | + const tempInput = path.join(tempDir, 'input.proto'); |
| 368 | + fs.copyFileSync(TEST_PROTO, tempInput); |
| 369 | + |
| 370 | + const opts: CleanupOptions = { |
| 371 | + input: tempInput, |
| 372 | + roots: ['SearchRequest', 'SearchResponse'] |
| 373 | + }; |
| 374 | + |
| 375 | + cleanupUnusedMessages(opts); |
| 376 | + |
| 377 | + // Should have written to input file |
| 378 | + expect(fs.existsSync(tempInput)).toBe(true); |
| 379 | + |
| 380 | + const output = parseProtoFile(tempInput); |
| 381 | + expect(output.messages.map(m => m.name)).not.toContain('UnusedMessage'); |
| 382 | + }); |
| 383 | + |
| 384 | + it('should return count of removed messages and enums', () => { |
| 385 | + const opts: CleanupOptions = { |
| 386 | + input: TEST_PROTO, |
| 387 | + output: outputPath, |
| 388 | + roots: ['SearchRequest', 'SearchResponse'] |
| 389 | + }; |
| 390 | + |
| 391 | + const result = cleanupUnusedMessages(opts); |
| 392 | + |
| 393 | + expect(typeof result.removedMessages).toBe('number'); |
| 394 | + expect(typeof result.removedEnums).toBe('number'); |
| 395 | + expect(result.removedMessages).toBeGreaterThanOrEqual(0); |
| 396 | + expect(result.removedEnums).toBeGreaterThanOrEqual(0); |
| 397 | + }); |
| 398 | +}); |
0 commit comments