Skip to content

Commit f2c1139

Browse files
committed
feat: refill pairing teammate pool after partial acceptance
1 parent c8632b1 commit f2c1139

3 files changed

Lines changed: 62 additions & 6 deletions

File tree

src/bot/__tests__/acceptPairingSlot.test.ts

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ describe('acceptPairingSlot', () => {
5454
.mockResolvedValue(makeInterview());
5555
jest
5656
.spyOn(PairingSessionCloserModule.pairingSessionCloser, 'closeIfComplete')
57-
.mockResolvedValue(undefined);
57+
.mockResolvedValue(false);
5858
userRepo.markNowAsLastReviewedDate = jest.fn().mockResolvedValue(undefined);
5959
chatService.updateDirectMessage = jest.fn().mockResolvedValue(undefined);
6060
});
@@ -123,6 +123,53 @@ describe('acceptPairingSlot', () => {
123123
expect(userRepo.markNowAsLastReviewedDate).not.toHaveBeenCalled();
124124
});
125125

126+
it('should request the next teammate if the session is still active after accept', async () => {
127+
const requestNextSpy = jest
128+
.spyOn(PairingRequestService.pairingRequestService, 'requestNextTeammate')
129+
.mockResolvedValue(undefined);
130+
131+
const param = buildMockActionParam();
132+
param.body.actions = [{ value: 'thread-1', action_id: 'pairing-submit-slots' } as any];
133+
param.body.user = { id: 'u1', name: 'Alice' } as any;
134+
param.body.message = { ts: 'msg-ts-1' } as any;
135+
param.body.state = {
136+
values: {
137+
'pairing-dm-slots': {
138+
'pairing-slot-selections': { selected_options: [{ value: 'slot-1' }] },
139+
},
140+
},
141+
} as any;
142+
143+
await acceptPairingSlot.handleSubmitSlots(param);
144+
145+
expect(requestNextSpy).toHaveBeenCalledWith(app, expect.anything());
146+
});
147+
148+
it('should not request the next teammate if the session was closed', async () => {
149+
const requestNextSpy = jest
150+
.spyOn(PairingRequestService.pairingRequestService, 'requestNextTeammate')
151+
.mockResolvedValue(undefined);
152+
jest
153+
.spyOn(PairingSessionCloserModule.pairingSessionCloser, 'closeIfComplete')
154+
.mockResolvedValue(true);
155+
156+
const param = buildMockActionParam();
157+
param.body.actions = [{ value: 'thread-1', action_id: 'pairing-submit-slots' } as any];
158+
param.body.user = { id: 'u1', name: 'Alice' } as any;
159+
param.body.message = { ts: 'msg-ts-1' } as any;
160+
param.body.state = {
161+
values: {
162+
'pairing-dm-slots': {
163+
'pairing-slot-selections': { selected_options: [{ value: 'slot-1' }] },
164+
},
165+
},
166+
} as any;
167+
168+
await acceptPairingSlot.handleSubmitSlots(param);
169+
170+
expect(requestNextSpy).not.toHaveBeenCalled();
171+
});
172+
126173
it('should call closeIfComplete after recording slot selections', async () => {
127174
const param = buildMockActionParam();
128175
param.body.actions = [{ value: 'thread-1', action_id: 'pairing-submit-slots' } as any];

src/bot/acceptPairingSlot.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export const acceptPairingSlot = {
6262
const user = await userRepo.find(userId);
6363
const userFormats = user?.formats ?? [];
6464

65-
await pairingRequestService.recordSlotSelections(
65+
const updatedInterview = await pairingRequestService.recordSlotSelections(
6666
interview,
6767
userId,
6868
selectedSlotIds,
@@ -86,7 +86,10 @@ export const acceptPairingSlot = {
8686
),
8787
]);
8888

89-
await pairingSessionCloser.closeIfComplete(acceptPairingSlot.app, threadId);
89+
const closed = await pairingSessionCloser.closeIfComplete(acceptPairingSlot.app, threadId);
90+
if (!closed) {
91+
await pairingRequestService.requestNextTeammate(acceptPairingSlot.app, updatedInterview);
92+
}
9093
});
9194
} catch (err: any) {
9295
await reportErrorAndContinue(acceptPairingSlot.app, 'Error handling pairing slot submit', {

src/services/PairingSessionCloser.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,15 @@ export function isSlotConfirmed(
2929
}
3030

3131
export const pairingSessionCloser = {
32-
async closeIfComplete(app: App, threadId: string): Promise<void> {
32+
/**
33+
* Returns true if the session was closed (or was already gone), false if it's still active.
34+
*/
35+
async closeIfComplete(app: App, threadId: string): Promise<boolean> {
3336
const interview = await pairingSessionsRepo.getByThreadIdOrUndefined(threadId);
3437

3538
if (!interview) {
3639
log.d('pairingSessionCloser', `Interview ${threadId} not found — likely already closed`);
37-
return;
40+
return true;
3841
}
3942

4043
const confirmedSlots = findConfirmedSlots(interview);
@@ -58,7 +61,7 @@ export const pairingSessionCloser = {
5861
await Promise.all(teammates.map(t => userRepo.markNowAsLastPairingReviewedDate(t.userId)));
5962
await pairingSessionsRepo.remove(threadId);
6063
reviewLockManager.releaseLock(threadId);
61-
return;
64+
return true;
6265
}
6366

6467
const isUnfulfilled = interview.pendingTeammates.length === 0;
@@ -71,6 +74,9 @@ export const pairingSessionCloser = {
7174
);
7275
await pairingSessionsRepo.remove(threadId);
7376
reviewLockManager.releaseLock(threadId);
77+
return true;
7478
}
79+
80+
return false;
7581
},
7682
};

0 commit comments

Comments
 (0)