fix: unexpected re-auth when auth-token is expired (#549)

This commit is contained in:
Mingholy
2025-09-09 11:34:05 +08:00
committed by GitHub
parent 60c136ad67
commit 621fe2e8ba
5 changed files with 448 additions and 144 deletions

View File

@@ -831,6 +831,32 @@ describe('getQwenOAuthClient', () => {
});
});
describe('CredentialsClearRequiredError', () => {
it('should create error with correct name and message', async () => {
const { CredentialsClearRequiredError } = await import('./qwenOAuth2.js');
const message = 'Test error message';
const originalError = { status: 400, response: 'Bad Request' };
const error = new CredentialsClearRequiredError(message, originalError);
expect(error.name).toBe('CredentialsClearRequiredError');
expect(error.message).toBe(message);
expect(error.originalError).toBe(originalError);
expect(error instanceof Error).toBe(true);
});
it('should work without originalError', async () => {
const { CredentialsClearRequiredError } = await import('./qwenOAuth2.js');
const message = 'Test error message';
const error = new CredentialsClearRequiredError(message);
expect(error.name).toBe('CredentialsClearRequiredError');
expect(error.message).toBe(message);
expect(error.originalError).toBeUndefined();
});
});
describe('clearQwenCredentials', () => {
it('should successfully clear credentials file', async () => {
const { promises: fs } = await import('node:fs');
@@ -902,21 +928,6 @@ describe('QwenOAuth2Client - Additional Error Scenarios', () => {
);
});
});
describe('isTokenValid edge cases', () => {
it('should return false when expiry_date is undefined', () => {
client.setCredentials({
access_token: 'token',
// expiry_date is undefined
});
// Access private method for testing
const isValid = (
client as unknown as { isTokenValid(): boolean }
).isTokenValid();
expect(isValid).toBe(false);
});
});
});
describe('getQwenOAuthClient - Enhanced Error Scenarios', () => {
@@ -1747,8 +1758,8 @@ describe('Enhanced Error Handling and Edge Cases', () => {
});
describe('QwenOAuth2Client getAccessToken enhanced scenarios', () => {
it('should handle SharedTokenManager failure and fall back to cached token', async () => {
// Set up client with valid credentials
it('should return undefined when SharedTokenManager fails (no fallback)', async () => {
// Set up client with valid credentials (but we don't use fallback anymore)
client.setCredentials({
access_token: 'fallback-token',
expiry_date: Date.now() + 3600000, // Valid for 1 hour
@@ -1772,7 +1783,9 @@ describe('Enhanced Error Handling and Edge Cases', () => {
const result = await client.getAccessToken();
expect(result.token).toBe('fallback-token');
// With our race condition fix, we no longer fall back to local credentials
// to ensure single source of truth
expect(result.token).toBeUndefined();
expect(consoleSpy).toHaveBeenCalledWith(
'Failed to get access token from shared manager:',
expect.any(Error),
@@ -2025,6 +2038,43 @@ describe('Enhanced Error Handling and Edge Cases', () => {
expect(fs.unlink).toHaveBeenCalled();
});
it('should throw CredentialsClearRequiredError on 400 error', async () => {
const { CredentialsClearRequiredError } = await import('./qwenOAuth2.js');
client.setCredentials({
refresh_token: 'expired-refresh',
});
const { promises: fs } = await import('node:fs');
vi.mocked(fs.unlink).mockResolvedValue(undefined);
const mockResponse = {
ok: false,
status: 400,
text: async () => 'Bad Request',
};
vi.mocked(global.fetch).mockResolvedValue(mockResponse as Response);
await expect(client.refreshAccessToken()).rejects.toThrow(
CredentialsClearRequiredError,
);
try {
await client.refreshAccessToken();
} catch (error) {
expect(error).toBeInstanceOf(CredentialsClearRequiredError);
if (error instanceof CredentialsClearRequiredError) {
expect(error.originalError).toEqual({
status: 400,
response: 'Bad Request',
});
}
}
expect(fs.unlink).toHaveBeenCalled();
});
it('should preserve existing refresh token when new one not provided', async () => {
const originalRefreshToken = 'original-refresh-token';
client.setCredentials({
@@ -2072,36 +2122,6 @@ describe('Enhanced Error Handling and Edge Cases', () => {
expect(credentials.resource_url).toBe('https://new-resource-url.com');
});
});
describe('isTokenValid edge cases', () => {
it('should return false for tokens expiring within buffer time', () => {
const nearExpiryTime = Date.now() + 15000; // 15 seconds from now (within 30s buffer)
client.setCredentials({
access_token: 'test-token',
expiry_date: nearExpiryTime,
});
const isValid = (
client as unknown as { isTokenValid(): boolean }
).isTokenValid();
expect(isValid).toBe(false);
});
it('should return true for tokens expiring well beyond buffer time', () => {
const futureExpiryTime = Date.now() + 120000; // 2 minutes from now (beyond 30s buffer)
client.setCredentials({
access_token: 'test-token',
expiry_date: futureExpiryTime,
});
const isValid = (
client as unknown as { isTokenValid(): boolean }
).isTokenValid();
expect(isValid).toBe(true);
});
});
});
describe('SharedTokenManager Integration in QwenOAuth2Client', () => {