@@ -511,6 +511,41 @@ describe("server-channels auto restart", () => {
|
511 | 511 | expect(account?.lastError).toContain("channel stop timed out"); |
512 | 512 | }); |
513 | 513 | |
| 514 | +it("resumes startup on the second recovery pass while the stale task is still pending", async () => { |
| 515 | +const startAccount = vi.fn(async ({ abortSignal }: { abortSignal: AbortSignal }) => { |
| 516 | +abortSignal.addEventListener("abort", () => {}, { once: true }); |
| 517 | +await new Promise<void>(() => {}); |
| 518 | +}); |
| 519 | +installTestRegistry( |
| 520 | +createTestPlugin({ |
| 521 | + startAccount, |
| 522 | +}), |
| 523 | +); |
| 524 | +const manager = createManager(); |
| 525 | + |
| 526 | +await manager.startChannels(); |
| 527 | +const recoveryStopTask = manager.stopChannel("discord", DEFAULT_ACCOUNT_ID, { |
| 528 | +manual: false, |
| 529 | +}); |
| 530 | +await vi.advanceTimersByTimeAsync(5_000); |
| 531 | +await recoveryStopTask; |
| 532 | + |
| 533 | +await manager.startChannel("discord", DEFAULT_ACCOUNT_ID); |
| 534 | +let account = manager.getRuntimeSnapshot().channelAccounts.discord?.[DEFAULT_ACCOUNT_ID]; |
| 535 | +expect(startAccount).toHaveBeenCalledTimes(1); |
| 536 | +expect(account?.running).toBe(false); |
| 537 | +expect(account?.restartPending).toBe(true); |
| 538 | + |
| 539 | +await manager.startChannel("discord", DEFAULT_ACCOUNT_ID); |
| 540 | + |
| 541 | +account = manager.getRuntimeSnapshot().channelAccounts.discord?.[DEFAULT_ACCOUNT_ID]; |
| 542 | +expect(startAccount).toHaveBeenCalledTimes(2); |
| 543 | +expect(account?.running).toBe(true); |
| 544 | +expect(account?.restartPending).toBe(false); |
| 545 | +expect(account?.reconnectAttempts).toBe(0); |
| 546 | +expect(account?.lastError).toBeNull(); |
| 547 | +}); |
| 548 | + |
514 | 549 | it("restarts immediately when recovery stop timeout settles with an error", async () => { |
515 | 550 | const rejectFirstTask = createDeferred(); |
516 | 551 | let startCount = 0; |
|