subagent-depth.test.ts 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. import { describe, expect, test } from 'bun:test';
  2. import { SubagentDepthTracker } from './subagent-depth';
  3. describe('SubagentDepthTracker', () => {
  4. describe('constructor', () => {
  5. test('uses DEFAULT_MAX_SUBAGENT_DEPTH (3) by default', () => {
  6. const tracker = new SubagentDepthTracker();
  7. expect(tracker).toBeDefined();
  8. });
  9. test('accepts custom max depth', () => {
  10. const tracker = new SubagentDepthTracker(5);
  11. expect(tracker).toBeDefined();
  12. });
  13. });
  14. describe('getDepth', () => {
  15. test('returns 0 for untracked sessions (root sessions)', () => {
  16. const tracker = new SubagentDepthTracker();
  17. expect(tracker.getDepth('root-session')).toBe(0);
  18. expect(tracker.getDepth('untracked-session')).toBe(0);
  19. });
  20. test('returns tracked depth for registered sessions', () => {
  21. const tracker = new SubagentDepthTracker();
  22. tracker.registerChild('root-session', 'child-session');
  23. expect(tracker.getDepth('child-session')).toBe(1);
  24. });
  25. });
  26. describe('registerChild', () => {
  27. test('tracks depth correctly (parent=0, child=1, grandchild=2)', () => {
  28. const tracker = new SubagentDepthTracker();
  29. expect(tracker.getDepth('root')).toBe(0);
  30. const allowed1 = tracker.registerChild('root', 'child1');
  31. expect(allowed1).toBe(true);
  32. expect(tracker.getDepth('child1')).toBe(1);
  33. const allowed2 = tracker.registerChild('child1', 'grandchild');
  34. expect(allowed2).toBe(true);
  35. expect(tracker.getDepth('grandchild')).toBe(2);
  36. });
  37. test('returns false when max depth exceeded (depth 4 > max 3)', () => {
  38. const tracker = new SubagentDepthTracker(3);
  39. const root = 'root';
  40. const child1 = 'child1';
  41. const child2 = 'child2';
  42. const child3 = 'child3';
  43. const child4 = 'child4';
  44. expect(tracker.registerChild(root, child1)).toBe(true);
  45. expect(tracker.registerChild(child1, child2)).toBe(true);
  46. expect(tracker.registerChild(child2, child3)).toBe(true);
  47. expect(tracker.registerChild(child3, child4)).toBe(false);
  48. });
  49. test('tracks across multiple branches independently', () => {
  50. const tracker = new SubagentDepthTracker();
  51. const root = 'root';
  52. const branch1Child = 'branch1-child';
  53. const branch2Child = 'branch2-child';
  54. const branch1Grandchild = 'branch1-grandchild';
  55. const branch2Grandchild = 'branch2-grandchild';
  56. tracker.registerChild(root, branch1Child);
  57. tracker.registerChild(branch1Child, branch1Grandchild);
  58. tracker.registerChild(root, branch2Child);
  59. tracker.registerChild(branch2Child, branch2Grandchild);
  60. expect(tracker.getDepth(branch1Child)).toBe(1);
  61. expect(tracker.getDepth(branch2Child)).toBe(1);
  62. expect(tracker.getDepth(branch1Grandchild)).toBe(2);
  63. expect(tracker.getDepth(branch2Grandchild)).toBe(2);
  64. });
  65. test('does not re-register existing session', () => {
  66. const tracker = new SubagentDepthTracker();
  67. const root = 'root';
  68. const child = 'child';
  69. tracker.registerChild(root, child);
  70. expect(tracker.getDepth(child)).toBe(1);
  71. tracker.registerChild(root, child);
  72. expect(tracker.getDepth(child)).toBe(1);
  73. });
  74. test('updates depth if child is re-registered from different parent', () => {
  75. const tracker = new SubagentDepthTracker();
  76. const root = 'root';
  77. const child1 = 'child1';
  78. const child2 = 'child2';
  79. const grandchild = 'grandchild';
  80. tracker.registerChild(root, child1);
  81. tracker.registerChild(child1, grandchild);
  82. expect(tracker.getDepth(grandchild)).toBe(2);
  83. tracker.registerChild(root, child2);
  84. tracker.registerChild(child2, grandchild);
  85. expect(tracker.getDepth(grandchild)).toBe(2);
  86. });
  87. });
  88. describe('cleanup', () => {
  89. test('removes a specific session', () => {
  90. const tracker = new SubagentDepthTracker();
  91. const root = 'root';
  92. const child1 = 'child1';
  93. const child2 = 'child2';
  94. tracker.registerChild(root, child1);
  95. tracker.registerChild(root, child2);
  96. expect(tracker.getDepth(child1)).toBe(1);
  97. expect(tracker.getDepth(child2)).toBe(1);
  98. tracker.cleanup(child1);
  99. expect(tracker.getDepth(child1)).toBe(0);
  100. expect(tracker.getDepth(child2)).toBe(1);
  101. });
  102. test('does not throw when cleaning up untracked session', () => {
  103. const tracker = new SubagentDepthTracker();
  104. expect(() => tracker.cleanup('untracked')).not.toThrow();
  105. });
  106. });
  107. describe('cleanupAll', () => {
  108. test('removes all sessions', () => {
  109. const tracker = new SubagentDepthTracker();
  110. const root = 'root';
  111. const child1 = 'child1';
  112. const child2 = 'child2';
  113. const grandchild = 'grandchild';
  114. tracker.registerChild(root, child1);
  115. tracker.registerChild(root, child2);
  116. tracker.registerChild(child1, grandchild);
  117. expect(tracker.getDepth(child1)).toBe(1);
  118. expect(tracker.getDepth(child2)).toBe(1);
  119. expect(tracker.getDepth(grandchild)).toBe(2);
  120. tracker.cleanupAll();
  121. expect(tracker.getDepth(child1)).toBe(0);
  122. expect(tracker.getDepth(child2)).toBe(0);
  123. expect(tracker.getDepth(grandchild)).toBe(0);
  124. });
  125. test('does not throw when called on empty tracker', () => {
  126. const tracker = new SubagentDepthTracker();
  127. expect(() => tracker.cleanupAll()).not.toThrow();
  128. });
  129. });
  130. });