import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; import { stripAnsi, isRateLimited, findRateLimitMessage } from '../src/patterns.js'; describe('stripAnsi', () => { it('removes bold codes', () => { assert.equal(stripAnsi('\x1b[1mlimit\x1b[0m'), 'limit'); }); it('removes color codes', () => { assert.equal(stripAnsi('\x1b[31mred\x1b[0m'), 'red'); }); it('removes cursor positioning', () => { assert.equal(stripAnsi('\x1b[2Jhello\x1b[H'), 'hello'); }); it('leaves plain text unchanged', () => { assert.equal(stripAnsi('plain text'), 'plain text'); }); it('handles mixed content', () => { assert.equal( stripAnsi('5-hour \x1b[1mlimit\x1b[0m reached - resets 3pm'), '5-hour limit reached - resets 3pm' ); }); }); describe('isRateLimited', () => { it('detects "5-hour limit reached"', () => { assert.equal(isRateLimited('5-hour limit reached - resets 3pm'), true); }); it('detects "usage limit" with reset', () => { assert.equal(isRateLimited('Claude usage limit reached. Resets at 2pm'), true); }); it('detects "out of extra usage"', () => { assert.equal(isRateLimited("You're out of extra usage · resets 3pm"), true); }); it('detects "try again in 5 hours"', () => { assert.equal(isRateLimited('Please try again in 5 hours'), true); }); it('detects "rate limit resets"', () => { assert.equal(isRateLimited('Rate limit hit. Resets at 4pm'), true); }); it('returns false for normal output', () => { assert.equal(isRateLimited('I can help you with that code'), false); }); it('returns false for empty string', () => { assert.equal(isRateLimited(''), false); }); it('detects rate limit with ANSI codes embedded', () => { assert.equal(isRateLimited('5-hour \x1b[1mlimit\x1b[0m reached - resets 3pm'), true); }); it('matches custom patterns', () => { assert.equal(isRateLimited('custom error xyz', [/custom error/i]), true); }); it('detects "You\'ve hit your limit" (real Claude Code message)', () => { assert.equal(isRateLimited("You've hit your limit · resets 3pm (Asia/Tbilisi)"), true); }); it('detects "hit the limit resets"', () => { assert.equal(isRateLimited('You hit the limit. Resets at 5pm'), true); }); it('detects "limit resets in: 3 hours"', () => { assert.equal(isRateLimited('limit resets in: 3 hours'), true); }); }); describe('stripAnsi (private-mode sequences)', () => { it('strips cursor hide sequence', () => { assert.equal(stripAnsi('\x1b[?25lhello\x1b[?25h'), 'hello'); }); it('strips bracketed paste mode', () => { assert.equal(stripAnsi('\x1b[?2004htext\x1b[?2004l'), 'text'); }); }); describe('findRateLimitMessage', () => { it('returns the matching line from multiline input', () => { const text = 'Some output\n5-hour limit reached - resets 3pm (Europe/Dublin)\nMore output'; assert.equal(findRateLimitMessage(text), '5-hour limit reached - resets 3pm (Europe/Dublin)'); }); it('returns null when no match', () => { assert.equal(findRateLimitMessage('normal output\nmore output'), null); }); });