| |
| |
| |
| |
|
|
| import { afterEach, describe, it } from 'node:test'; |
| import assert from 'node:assert/strict'; |
| import { |
| addAccountByKey, removeAccount, getAccountInternal, |
| looksLikeBanSignal, reportBanSignal, reportSuccess, |
| } from '../src/auth.js'; |
|
|
| const created = []; |
| function mkAccount(label = 'ban-test') { |
| const a = addAccountByKey('sk-ban-' + Date.now() + '-' + Math.random().toString(36).slice(2, 8), label); |
| created.push(a.id); |
| return a; |
| } |
|
|
| afterEach(() => { |
| while (created.length) removeAccount(created.pop()); |
| }); |
|
|
| describe('looksLikeBanSignal (audit windsurf-assistant-pub)', () => { |
| const positives = [ |
| 'Your account has been suspended for terms-of-service violation', |
| 'account_disabled', |
| 'Account banned by upstream', |
| 'User suspended due to abuse', |
| 'Subscription cancelled', |
| 'subscription expired', |
| 'Authentication failed: invalid credentials', |
| 'Invalid API key', |
| 'API key revoked', |
| 'API key disabled', |
| 'unauthorized: account does not exist', |
| '账号已停用', |
| '账号封禁', |
| '用户已禁用', |
| '订阅已过期', |
| ]; |
| const negatives = [ |
| 'rate limit exceeded', |
| 'too many requests', |
| 'context deadline exceeded', |
| 'internal error occurred (error ID: abc)', |
| 'cascade transport failure', |
| 'panel state not found', |
| 'temporary upstream stall', |
| '', |
| null, |
| undefined, |
| 42, |
| ]; |
|
|
| const labelOf = (msg) => { |
| const s = (msg === null) ? 'null' : (msg === undefined) ? 'undefined' : String(msg); |
| return s.slice(0, 60).replace(/\s+/g, ' '); |
| }; |
| for (const msg of positives) { |
| it(`detects ban signal: ${labelOf(msg)}`, () => { |
| assert.equal(looksLikeBanSignal(msg), true); |
| }); |
| } |
| for (const msg of negatives) { |
| it(`ignores non-ban: ${labelOf(msg) || '(empty)'}`, () => { |
| assert.equal(looksLikeBanSignal(msg), false); |
| }); |
| } |
| }); |
|
|
| describe('reportBanSignal: 2-strike → status=banned', () => { |
| it('first ban-shaped error does NOT flip status', () => { |
| const a = mkAccount(); |
| reportBanSignal(a.apiKey, 'Account suspended'); |
| assert.equal(getAccountInternal(a.id).status, 'active', 'one strike must not ban'); |
| }); |
|
|
| it('two ban-shaped errors within window → status=banned', () => { |
| const a = mkAccount(); |
| reportBanSignal(a.apiKey, 'Account suspended'); |
| reportBanSignal(a.apiKey, 'Invalid API key'); |
| const after = getAccountInternal(a.id); |
| assert.equal(after.status, 'banned', 'two strikes must promote to banned'); |
| assert.ok(after.bannedAt > 0, 'bannedAt timestamp must be set'); |
| assert.ok(after.bannedReason, 'bannedReason must be set'); |
| }); |
|
|
| it('reportSuccess between two ban signals resets the streak', () => { |
| const a = mkAccount(); |
| reportBanSignal(a.apiKey, 'Authentication failed'); |
| reportSuccess(a.apiKey); |
| reportBanSignal(a.apiKey, 'Authentication failed'); |
| assert.equal(getAccountInternal(a.id).status, 'active', 'success must clear ban-streak'); |
| }); |
|
|
| it('signal on unknown apiKey is a no-op', () => { |
| const result = reportBanSignal('sk-does-not-exist', 'Account suspended'); |
| assert.equal(result, false); |
| }); |
| }); |
|
|