cli.spec.ts 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. import { describe, expect, it } from 'vitest'
  2. import {
  3. MOCK_LLM_CLI_USAGE,
  4. parseMockLlmCliArgs,
  5. } from '../src/cli.ts'
  6. describe('mock LLM server CLI parser', () => {
  7. it('returns help without requiring a sequence', () => {
  8. expect(parseMockLlmCliArgs(['--help'])).toEqual({ kind: 'help' })
  9. expect(MOCK_LLM_CLI_USAGE).toContain('--sequence')
  10. })
  11. it('parses every request and listener option', () => {
  12. expect(parseMockLlmCliArgs([
  13. '--sequence', 'connection_refused,partial_disconnect,success',
  14. '--host', 'localhost',
  15. '--port', '9010',
  16. '--api-key', 'mock-key',
  17. '--listen-delay-ms', '100',
  18. '--repeat-last',
  19. '--success-text', 'done',
  20. '--partial-text', 'half',
  21. '--reasoning-text', 'think',
  22. '--chunk-size', '2',
  23. '--chunk-delay-ms', '3',
  24. '--disconnect-delay-ms', '4',
  25. '--retry-after-ms', '5000',
  26. '--request-id', 'request-1',
  27. '--tool-name', 'lookup',
  28. '--tool-arguments', '{"id":1}',
  29. ])).toEqual({
  30. kind: 'run',
  31. config: {
  32. startsUnavailable: true,
  33. listenDelayMs: 100,
  34. server: {
  35. sequence: ['partial_disconnect', 'success'],
  36. host: 'localhost',
  37. port: 9010,
  38. apiKey: 'mock-key',
  39. repeatLast: true,
  40. successText: 'done',
  41. partialText: 'half',
  42. reasoningText: 'think',
  43. chunkSize: 2,
  44. chunkDelayMs: 3,
  45. disconnectDelayMs: 4,
  46. retryAfterMs: 5000,
  47. requestId: 'request-1',
  48. toolName: 'lookup',
  49. toolArguments: '{"id":1}',
  50. },
  51. },
  52. })
  53. })
  54. it('uses standalone defaults for an ordinary sequence', () => {
  55. expect(parseMockLlmCliArgs(['--sequence', 'success'])).toEqual({
  56. kind: 'run',
  57. config: {
  58. startsUnavailable: false,
  59. listenDelayMs: 0,
  60. server: {
  61. sequence: ['success'],
  62. port: 8000,
  63. repeatLast: false,
  64. },
  65. },
  66. })
  67. })
  68. it('uses the default unavailable interval', () => {
  69. const result = parseMockLlmCliArgs(['--sequence', 'connection_refused,success', '--port', '8001'])
  70. expect(result).toMatchObject({
  71. kind: 'run',
  72. config: { startsUnavailable: true, listenDelayMs: 750 },
  73. })
  74. })
  75. it('parses a reproducible weighted random profile', () => {
  76. expect(parseMockLlmCliArgs([
  77. '--sequence', 'random',
  78. '--repeat-last',
  79. '--seed', '42',
  80. '--random-weights', 'success=8,partial_disconnect=2',
  81. ])).toEqual({
  82. kind: 'run',
  83. config: {
  84. startsUnavailable: false,
  85. listenDelayMs: 0,
  86. server: {
  87. sequence: ['random'],
  88. port: 8000,
  89. repeatLast: true,
  90. randomSeed: 42,
  91. randomWeights: { success: 8, partial_disconnect: 2 },
  92. },
  93. },
  94. })
  95. })
  96. it.each([
  97. [[], /--sequence is required/],
  98. // Tokenizer-level failures carry node:util parseArgs's own messages.
  99. [['--wat'], /Unknown option '--wat'/],
  100. [['--wat', 'x'], /Unknown option '--wat'/],
  101. [['--port'], /Option '--port <value>' argument missing/],
  102. [['--sequence', 'success', 'stray'], /Unexpected argument 'stray'/],
  103. [['--port', 'NaN', '--sequence', 'success'], /finite number/],
  104. [['--sequence', 'success,'], /non-empty/],
  105. [['--sequence', 'success,connection_refused'], /only as the first/],
  106. [['--sequence', 'connection_refused'], /must be followed/],
  107. [['--sequence', 'unknown'], /unknown behavior/],
  108. [['--sequence', 'connection_refused,success', '--port', '0'], /nonzero/],
  109. [['--sequence', 'success', '--listen-delay-ms', '5'], /requires connection_refused/],
  110. // `=` syntax: a space-separated leading-dash value is a tokenizer error, not a bounds probe.
  111. [['--sequence', 'connection_refused,success', '--listen-delay-ms=-1'], /integer between 0 and 2147483647/],
  112. [['--sequence', 'connection_refused,success', '--listen-delay-ms', '1.5'], /integer between 0 and 2147483647/],
  113. [['--sequence', 'connection_refused,success', '--listen-delay-ms', '2147483648'], /integer between 0 and 2147483647/],
  114. [['--sequence', 'success', '--seed', '1'], /require random/],
  115. [['--sequence', 'random', '--random-weights', 'success'], /expects behavior=weight/],
  116. [['--sequence', 'random', '--random-weights', 'random=1'], /concrete behavior/],
  117. [['--sequence', 'random', '--random-weights', 'success=1,success=2'], /duplicate/],
  118. [['--sequence', 'random', '--random-weights', 'success=nope'], /finite number/],
  119. ])('rejects invalid argv %#', (argv, expected) => {
  120. expect(() => parseMockLlmCliArgs(argv)).toThrow(expected)
  121. })
  122. })