import { describe, it, expect, beforeEach, jest } from '@jest/globals';
import { TokenLimiterPlugin } from '../../../src/guardrails/plugins/token-limiter';
import { createGuardrailContext } from '../../../src/guardrails/context';
// Mock logger to avoid console noise during tests
jest.mock('../../../src/utils/logger');
describe('TokenLimiterPlugin', () => {
let plugin: TokenLimiterPlugin;
beforeEach(async () => {
plugin = new TokenLimiterPlugin();
});
describe('initialization', () => {
it('should initialize with default values', async () => {
await plugin.initialize({ enabled: true });
expect(plugin.enabled).toBe(true);
expect(plugin.name).toBe('token_limiter');
expect(plugin.phases).toContain('pre_request');
expect(plugin.getLimits().maxInputTokens).toBe(8192);
});
it('should initialize with custom config', async () => {
await plugin.initialize({
enabled: true,
priority: 15,
max_input_tokens: 4096,
max_output_tokens: 2048,
warn_at_percentage: 90,
});
expect(plugin.priority).toBe(15);
const limits = plugin.getLimits();
expect(limits.maxInputTokens).toBe(4096);
expect(limits.maxOutputTokens).toBe(2048);
});
});
describe('token estimation', () => {
it('should estimate tokens from text', async () => {
await plugin.initialize({ enabled: true });
// ~4 chars per token
expect(plugin.estimateTokenCount('Hello')).toBeGreaterThan(0);
expect(plugin.estimateTokenCount('Hello world')).toBeGreaterThan(plugin.estimateTokenCount('Hello'));
});
it('should return 0 for empty text', async () => {
await plugin.initialize({ enabled: true });
expect(plugin.estimateTokenCount('')).toBe(0);
});
});
describe('token limiting', () => {
it('should allow prompts under the limit', async () => {
await plugin.initialize({
enabled: true,
max_input_tokens: 1000,
});
const shortPrompt = 'Hello world'; // ~7 tokens
const context = createGuardrailContext({ prompt: shortPrompt });
const result = await plugin.execute('pre_request', context);
expect(result.action).toBe('allow');
});
it('should block prompts over the limit', async () => {
await plugin.initialize({
enabled: true,
max_input_tokens: 10, // Very small limit
});
// Create a prompt that exceeds the limit (~4 chars per token)
const longPrompt = 'This is a longer prompt that will definitely exceed our tiny token limit';
const context = createGuardrailContext({ prompt: longPrompt });
const result = await plugin.execute('pre_request', context);
expect(result.action).toBe('block');
expect(result.blockedBy).toBe('token_limiter');
expect(result.blockReason).toContain('Token limit exceeded');
});
it('should include messages in token count', async () => {
await plugin.initialize({
enabled: true,
max_input_tokens: 20,
});
const context = createGuardrailContext({
prompt: 'Short',
messages: [
{ role: 'user', content: 'This is a message with many tokens that should push us over the limit', timestamp: new Date() },
],
});
const result = await plugin.execute('pre_request', context);
expect(result.action).toBe('block');
});
});
describe('warnings', () => {
it('should add warning when approaching limit', async () => {
await plugin.initialize({
enabled: true,
max_input_tokens: 100,
warn_at_percentage: 50, // Warn at 50%
});
// Create prompt that's ~60-80% of limit
const prompt = 'A'.repeat(300); // ~75 tokens
const context = createGuardrailContext({ prompt });
await plugin.execute('pre_request', context);
const warnings = context.violations.filter((v) => v.severity === 'warning');
expect(warnings.length).toBeGreaterThan(0);
});
it('should not warn when well under limit', async () => {
await plugin.initialize({
enabled: true,
max_input_tokens: 1000,
warn_at_percentage: 80,
});
const shortPrompt = 'Hello';
const context = createGuardrailContext({ prompt: shortPrompt });
await plugin.execute('pre_request', context);
const warnings = context.violations.filter((v) => v.severity === 'warning');
expect(warnings.length).toBe(0);
});
});
describe('phase handling', () => {
it('should process pre_request and post_response phases', async () => {
await plugin.initialize({ enabled: true });
expect(plugin.phases).toContain('pre_request');
expect(plugin.phases).toContain('post_response');
});
it('should allow other phases', async () => {
await plugin.initialize({ enabled: true });
const context = createGuardrailContext({ prompt: 'test' });
// pre_tool_input should pass through
const result = await plugin.execute('pre_tool_input', context);
expect(result.action).toBe('allow');
});
});
describe('output token limiting', () => {
it('should allow responses under the limit', async () => {
await plugin.initialize({
enabled: true,
max_output_tokens: 1000,
});
const context = createGuardrailContext({ prompt: 'test' });
context.response = 'Short response';
const result = await plugin.execute('post_response', context);
expect(result.action).toBe('allow');
});
it('should block responses over the limit', async () => {
await plugin.initialize({
enabled: true,
max_output_tokens: 10, // Very small limit
});
const context = createGuardrailContext({ prompt: 'test' });
context.response = 'This is a longer response that will definitely exceed our tiny token limit for output';
const result = await plugin.execute('post_response', context);
expect(result.action).toBe('block');
expect(result.blockedBy).toBe('token_limiter');
expect(result.blockReason).toContain('Output token limit exceeded');
});
it('should skip output check if no max_output_tokens configured', async () => {
await plugin.initialize({
enabled: true,
// No max_output_tokens
});
const context = createGuardrailContext({ prompt: 'test' });
context.response = 'A'.repeat(10000); // Very long response
const result = await plugin.execute('post_response', context);
expect(result.action).toBe('allow');
});
it('should add warning when response approaches output limit', async () => {
await plugin.initialize({
enabled: true,
max_output_tokens: 100,
warn_at_percentage: 50, // Warn at 50%
});
// Create response that's ~60-80% of limit
const context = createGuardrailContext({ prompt: 'test' });
context.response = 'A'.repeat(300); // ~75 tokens
await plugin.execute('post_response', context);
const warnings = context.violations.filter((v) => v.severity === 'warning');
expect(warnings.length).toBeGreaterThan(0);
expect(warnings[0].rule).toBe('max_output_tokens_warning');
});
});
});