trigger.test.ts 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. import { describe, expect, it } from 'bun:test'
  2. import type { Ability, Triggers } from '../src/types/index.js'
  3. function matchesTrigger(text: string, triggers: Triggers | undefined): boolean {
  4. if (!triggers) return false
  5. const lowerText = text.toLowerCase()
  6. if (triggers.keywords) {
  7. for (const keyword of triggers.keywords) {
  8. if (lowerText.includes(keyword.toLowerCase())) {
  9. return true
  10. }
  11. }
  12. }
  13. if (triggers.patterns) {
  14. for (const pattern of triggers.patterns) {
  15. try {
  16. if (new RegExp(pattern, 'i').test(text)) {
  17. return true
  18. }
  19. } catch {
  20. continue
  21. }
  22. }
  23. }
  24. return false
  25. }
  26. describe('Trigger Detection', () => {
  27. describe('keyword matching', () => {
  28. it('should match exact keyword', () => {
  29. const triggers: Triggers = { keywords: ['deploy'] }
  30. expect(matchesTrigger('deploy to production', triggers)).toBe(true)
  31. })
  32. it('should match keyword case-insensitively', () => {
  33. const triggers: Triggers = { keywords: ['Deploy'] }
  34. expect(matchesTrigger('DEPLOY now', triggers)).toBe(true)
  35. })
  36. it('should match keyword as substring', () => {
  37. const triggers: Triggers = { keywords: ['ship'] }
  38. expect(matchesTrigger('ship it!', triggers)).toBe(true)
  39. })
  40. it('should not match when keyword not present', () => {
  41. const triggers: Triggers = { keywords: ['deploy'] }
  42. expect(matchesTrigger('release the code', triggers)).toBe(false)
  43. })
  44. it('should match any of multiple keywords', () => {
  45. const triggers: Triggers = { keywords: ['deploy', 'release', 'ship'] }
  46. expect(matchesTrigger('release v1.0', triggers)).toBe(true)
  47. expect(matchesTrigger('ship it', triggers)).toBe(true)
  48. })
  49. })
  50. describe('pattern matching', () => {
  51. it('should match regex pattern', () => {
  52. const triggers: Triggers = { patterns: ['deploy.*prod'] }
  53. expect(matchesTrigger('deploy to production', triggers)).toBe(true)
  54. })
  55. it('should match version pattern', () => {
  56. const triggers: Triggers = { patterns: ['v\\d+\\.\\d+\\.\\d+'] }
  57. expect(matchesTrigger('release v1.2.3', triggers)).toBe(true)
  58. })
  59. it('should not match invalid pattern gracefully', () => {
  60. const triggers: Triggers = { patterns: ['[invalid(regex'] }
  61. expect(matchesTrigger('some text', triggers)).toBe(false)
  62. })
  63. it('should match case-insensitively', () => {
  64. const triggers: Triggers = { patterns: ['DEPLOY'] }
  65. expect(matchesTrigger('deploy now', triggers)).toBe(true)
  66. })
  67. })
  68. describe('combined triggers', () => {
  69. it('should match keyword OR pattern', () => {
  70. const triggers: Triggers = {
  71. keywords: ['ship it'],
  72. patterns: ['deploy.*prod']
  73. }
  74. expect(matchesTrigger('ship it now', triggers)).toBe(true)
  75. expect(matchesTrigger('deploy to prod', triggers)).toBe(true)
  76. })
  77. it('should return false when no triggers defined', () => {
  78. expect(matchesTrigger('anything', undefined)).toBe(false)
  79. expect(matchesTrigger('anything', {})).toBe(false)
  80. })
  81. })
  82. describe('ability trigger detection', () => {
  83. it('should detect ability from user message', () => {
  84. const abilities: Ability[] = [
  85. {
  86. name: 'deploy',
  87. description: 'Deploy to production',
  88. triggers: { keywords: ['deploy', 'ship it'] },
  89. steps: [{ id: 'test', type: 'script', run: 'echo test' }]
  90. },
  91. {
  92. name: 'test',
  93. description: 'Run tests',
  94. triggers: { keywords: ['run tests', 'test suite'] },
  95. steps: [{ id: 'test', type: 'script', run: 'npm test' }]
  96. }
  97. ]
  98. const detectAbility = (text: string): Ability | null => {
  99. for (const ability of abilities) {
  100. if (matchesTrigger(text, ability.triggers)) {
  101. return ability
  102. }
  103. }
  104. return null
  105. }
  106. expect(detectAbility('please deploy to staging')?.name).toBe('deploy')
  107. expect(detectAbility('ship it!')?.name).toBe('deploy')
  108. expect(detectAbility('run tests please')?.name).toBe('test')
  109. expect(detectAbility('check the code')).toBe(null)
  110. })
  111. })
  112. })