|
1 | 1 | import { describe, expect, it } from 'bun:test' |
2 | 2 | import type { Session } from './schemas' |
3 | | -import { getPendingRequestKinds, toSessionSummary } from './sessionSummary' |
| 3 | +import { |
| 4 | + PENDING_REQUEST_SUMMARY_CAP, |
| 5 | + getPendingRequestKinds, |
| 6 | + getPendingRequests, |
| 7 | + toSessionSummary |
| 8 | +} from './sessionSummary' |
4 | 9 |
|
5 | 10 | function makeSession(overrides: Partial<Session> = {}): Session { |
6 | 11 | return { |
@@ -87,4 +92,101 @@ describe('toSessionSummary', () => { |
87 | 92 |
|
88 | 93 | expect(summary.metadata?.lifecycleState).toBe('archived') |
89 | 94 | }) |
| 95 | + |
| 96 | + it('includes structured pendingRequests for hover-tooltip copy', () => { |
| 97 | + const summary = toSessionSummary(makeSession({ |
| 98 | + updatedAt: 5000, |
| 99 | + agentState: { |
| 100 | + requests: { |
| 101 | + req1: { tool: 'Bash', arguments: {}, createdAt: 100 }, |
| 102 | + req2: { tool: 'AskUserQuestion', arguments: {}, createdAt: 50 }, |
| 103 | + req3: { tool: 'Edit', arguments: {} } |
| 104 | + } |
| 105 | + } |
| 106 | + })) |
| 107 | + |
| 108 | + expect(summary.pendingRequestsCount).toBe(3) |
| 109 | + expect(summary.pendingRequestKinds).toEqual(['permission', 'input']) |
| 110 | + expect(summary.pendingRequests).toHaveLength(3) |
| 111 | + expect(summary.pendingRequests[0]).toEqual({ |
| 112 | + id: 'req2', |
| 113 | + kind: 'input', |
| 114 | + tool: 'AskUserQuestion', |
| 115 | + since: 50 |
| 116 | + }) |
| 117 | + expect(summary.pendingRequests[1]).toEqual({ |
| 118 | + id: 'req1', |
| 119 | + kind: 'permission', |
| 120 | + tool: 'Bash', |
| 121 | + since: 100 |
| 122 | + }) |
| 123 | + expect(summary.pendingRequests[2]).toEqual({ |
| 124 | + id: 'req3', |
| 125 | + kind: 'permission', |
| 126 | + tool: 'Edit', |
| 127 | + since: 5000 |
| 128 | + }) |
| 129 | + }) |
| 130 | + |
| 131 | + it('returns empty pendingRequests when agentState has no requests', () => { |
| 132 | + const summary = toSessionSummary(makeSession({ agentState: null })) |
| 133 | + expect(summary.pendingRequests).toEqual([]) |
| 134 | + }) |
| 135 | +}) |
| 136 | + |
| 137 | +describe('getPendingRequests', () => { |
| 138 | + it('caps the array length while leaving pendingRequestsCount untouched', () => { |
| 139 | + const requests: Record<string, { tool: string; arguments: unknown; createdAt: number }> = {} |
| 140 | + for (let i = 0; i < PENDING_REQUEST_SUMMARY_CAP + 3; i += 1) { |
| 141 | + requests[`req-${i.toString().padStart(2, '0')}`] = { |
| 142 | + tool: 'Bash', |
| 143 | + arguments: {}, |
| 144 | + createdAt: i |
| 145 | + } |
| 146 | + } |
| 147 | + const session = makeSession({ agentState: { requests } }) |
| 148 | + |
| 149 | + const slice = getPendingRequests(session) |
| 150 | + expect(slice).toHaveLength(PENDING_REQUEST_SUMMARY_CAP) |
| 151 | + // Oldest-first → the first `cap` items by createdAt should win. |
| 152 | + expect(slice.map(r => r.id)).toEqual( |
| 153 | + Array.from({ length: PENDING_REQUEST_SUMMARY_CAP }, (_, i) => `req-${i.toString().padStart(2, '0')}`) |
| 154 | + ) |
| 155 | + |
| 156 | + const summary = toSessionSummary(session) |
| 157 | + expect(summary.pendingRequestsCount).toBe(PENDING_REQUEST_SUMMARY_CAP + 3) |
| 158 | + expect(summary.pendingRequests).toHaveLength(PENDING_REQUEST_SUMMARY_CAP) |
| 159 | + }) |
| 160 | + |
| 161 | + it('breaks ties on createdAt by id (stable across hub restarts)', () => { |
| 162 | + const session = makeSession({ |
| 163 | + agentState: { |
| 164 | + requests: { |
| 165 | + 'req-b': { tool: 'Bash', arguments: {}, createdAt: 100 }, |
| 166 | + 'req-a': { tool: 'Edit', arguments: {}, createdAt: 100 } |
| 167 | + } |
| 168 | + } |
| 169 | + }) |
| 170 | + const slice = getPendingRequests(session) |
| 171 | + expect(slice.map(r => r.id)).toEqual(['req-a', 'req-b']) |
| 172 | + }) |
| 173 | +}) |
| 174 | + |
| 175 | +describe('getPendingRequestKinds', () => { |
| 176 | + it('reads from the FULL request set (not the capped pendingRequests slice)', () => { |
| 177 | + const requests: Record<string, { tool: string; arguments: unknown; createdAt: number }> = {} |
| 178 | + // First CAP requests are all permission, last one is input — must still |
| 179 | + // surface 'input' even though it would fall outside the capped slice. |
| 180 | + for (let i = 0; i < PENDING_REQUEST_SUMMARY_CAP; i += 1) { |
| 181 | + requests[`perm-${i}`] = { tool: 'Bash', arguments: {}, createdAt: i } |
| 182 | + } |
| 183 | + requests['ask'] = { |
| 184 | + tool: 'AskUserQuestion', |
| 185 | + arguments: {}, |
| 186 | + createdAt: PENDING_REQUEST_SUMMARY_CAP + 100 |
| 187 | + } |
| 188 | + |
| 189 | + const kinds = getPendingRequestKinds(makeSession({ agentState: { requests } })) |
| 190 | + expect(kinds).toEqual(['permission', 'input']) |
| 191 | + }) |
90 | 192 | }) |
0 commit comments