mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-07-10 01:29:17 +00:00
fix(channel): keep qqbot token refresh retrying (#5414)
This commit is contained in:
parent
c8e8c5b2df
commit
617eb20081
2 changed files with 53 additions and 12 deletions
|
|
@ -543,22 +543,26 @@ export class QQChannel extends ChannelBase {
|
|||
process.stderr.write(
|
||||
`[QQ:${this.name}] Token refresh failed: ${e}, retrying in 60s\n`,
|
||||
);
|
||||
// Retry fetchToken directly instead of going through
|
||||
// scheduleTokenRefresh (which would add another ~60s of
|
||||
// delay from the stale tokenExpiresAt).
|
||||
this.tokenRefreshTimer = setTimeout(() => {
|
||||
if (this.disposed) return;
|
||||
this.fetchToken().catch(() => {
|
||||
process.stderr.write(
|
||||
`[QQ:${this.name}] Token refresh failed again after retry\n`,
|
||||
);
|
||||
});
|
||||
}, 60_000);
|
||||
this.scheduleTokenRefreshRetry();
|
||||
});
|
||||
}, delay);
|
||||
}
|
||||
}
|
||||
|
||||
private scheduleTokenRefreshRetry(): void {
|
||||
if (this.disposed) return;
|
||||
this.stopTokenRefresh();
|
||||
this.tokenRefreshTimer = setTimeout(() => {
|
||||
this.fetchToken().catch((e) => {
|
||||
if (this.disposed) return;
|
||||
process.stderr.write(
|
||||
`[QQ:${this.name}] Token refresh failed: ${e}, retrying in 60s\n`,
|
||||
);
|
||||
this.scheduleTokenRefreshRetry();
|
||||
});
|
||||
}, 60_000);
|
||||
}
|
||||
|
||||
private stopTokenRefresh(): void {
|
||||
if (this.tokenRefreshTimer) {
|
||||
clearTimeout(this.tokenRefreshTimer);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { isValidChatId, hasMarkdownSyntax, splitText } from './QQChannel.js';
|
||||
|
||||
const {
|
||||
|
|
@ -106,6 +106,10 @@ const { QQChannel } = await import('./QQChannel.js');
|
|||
type QQChannelOptions = ConstructorParameters<typeof QQChannel>[3];
|
||||
type QQChannelRouter = NonNullable<QQChannelOptions>['router'];
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
/** Create a mock Response-like object for sendQQMessage. */
|
||||
function mockResponse(
|
||||
ok: boolean,
|
||||
|
|
@ -471,6 +475,39 @@ describe('sendMessage', () => {
|
|||
expect(mockFetchAccessToken).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('keeps retrying scheduled token refresh failures until one succeeds', async () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(new Date('2026-01-01T00:00:00Z'));
|
||||
|
||||
const ch = makeChannel();
|
||||
const chp = ch as unknown as Record<string, unknown>;
|
||||
chp['tokenExpiresAt'] = Date.now() + 120_000;
|
||||
mockFetchAccessToken
|
||||
.mockRejectedValueOnce(new Error('token endpoint down'))
|
||||
.mockRejectedValueOnce(new Error('still down'))
|
||||
.mockResolvedValueOnce({
|
||||
accessToken: 'recovered-token',
|
||||
expiresIn: 7200,
|
||||
});
|
||||
|
||||
(chp['scheduleTokenRefresh'] as () => void).call(ch);
|
||||
|
||||
await vi.advanceTimersByTimeAsync(60_000);
|
||||
expect(mockFetchAccessToken).toHaveBeenCalledTimes(1);
|
||||
|
||||
await vi.advanceTimersByTimeAsync(60_000);
|
||||
expect(mockFetchAccessToken).toHaveBeenCalledTimes(2);
|
||||
|
||||
await vi.advanceTimersByTimeAsync(60_000);
|
||||
expect(mockFetchAccessToken).toHaveBeenCalledTimes(3);
|
||||
expect(chp['accessToken']).toBe('recovered-token');
|
||||
|
||||
await vi.advanceTimersByTimeAsync(60_000);
|
||||
expect(mockFetchAccessToken).toHaveBeenCalledTimes(3);
|
||||
|
||||
ch.disconnect();
|
||||
});
|
||||
|
||||
it('catches thrown sendQQMessage errors and stops sending', async () => {
|
||||
const ch = makeChannel({ chatType: 'c2c' });
|
||||
mockSendQQMessage.mockRejectedValue(new Error('network down'));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue