import { describe, it, expect, beforeEach } from 'vitest';
import {
TokenEconomicsTracker,
createTokenEconomicsTracker,
} from '../token-economics.js';
describe('TokenEconomicsTracker', () => {
let tracker: TokenEconomicsTracker;
beforeEach(() => {
tracker = new TokenEconomicsTracker();
});
describe('token estimation', () => {
it('should estimate tokens (~4 chars per token)', () => {
const text = 'This is a test'; // 14 chars
const tokens = tracker.estimateTokens(text);
expect(tokens).toBe(4); // ceil(14/4) = 4
});
it('should handle empty string', () => {
expect(tracker.estimateTokens('')).toBe(0);
});
it('should handle long text', () => {
const text = 'a'.repeat(1000);
expect(tracker.estimateTokens(text)).toBe(250);
});
});
describe('recording creation', () => {
it('should record memory creation', () => {
tracker.recordCreation('entry1', 'Test content here');
const stats = tracker.getStats();
expect(stats.totalObservations).toBe(1);
expect(stats.totalDiscoveryTokens).toBeGreaterThan(0);
});
it('should track multiple creations', () => {
tracker.recordCreation('entry1', 'First content');
tracker.recordCreation('entry2', 'Second content');
tracker.recordCreation('entry3', 'Third content');
const stats = tracker.getStats();
expect(stats.totalObservations).toBe(3);
});
it('should track per-entry metrics', () => {
tracker.recordCreation('entry1', 'Test content');
const metrics = tracker.getEntryMetrics('entry1');
expect(metrics.length).toBe(1);
expect(metrics[0].entryId).toBe('entry1');
expect(metrics[0].discoveryTokens).toBeGreaterThan(0);
expect(metrics[0].accessCount).toBe(0);
});
});
describe('recording retrieval', () => {
it('should record memory retrieval', () => {
tracker.recordCreation('entry1', 'Test content');
tracker.recordRetrieval('entry1', 'Test content');
const stats = tracker.getStats();
expect(stats.totalReadTokens).toBeGreaterThan(0);
});
it('should update access count', () => {
tracker.recordCreation('entry1', 'Test content');
tracker.recordRetrieval('entry1', 'Test content');
tracker.recordRetrieval('entry1', 'Test content');
const metrics = tracker.getEntryMetrics('entry1');
expect(metrics[0].accessCount).toBe(2);
});
it('should calculate value score', () => {
tracker.recordCreation('entry1', 'A'.repeat(100)); // 25 discovery tokens
tracker.recordRetrieval('entry1', 'B'.repeat(40)); // 10 read tokens
const metrics = tracker.getEntryMetrics('entry1');
// savings = 25 - 10 = 15
// valueScore = 15 * 1 = 15
expect(metrics[0].savings).toBe(15);
expect(metrics[0].valueScore).toBe(15);
});
});
describe('statistics', () => {
it('should calculate savings', () => {
tracker.recordCreation('entry1', 'A'.repeat(100));
tracker.recordRetrieval('entry1', 'B'.repeat(40));
const stats = tracker.getStats();
expect(stats.totalSavings).toBe(15); // 25 - 10
});
it('should calculate savings percentage', () => {
tracker.recordCreation('entry1', 'A'.repeat(100));
tracker.recordRetrieval('entry1', 'B'.repeat(40));
const stats = tracker.getStats();
// (25 - 10) / 25 * 100 = 60%
expect(stats.savingsPercent).toBe(60);
});
it('should handle zero discovery tokens', () => {
const stats = tracker.getStats();
expect(stats.savingsPercent).toBe(0);
expect(stats.avgTokensPerObservation).toBe(0);
});
it('should calculate average tokens per observation', () => {
tracker.recordCreation('entry1', 'A'.repeat(40)); // 10 tokens
tracker.recordCreation('entry2', 'B'.repeat(80)); // 20 tokens
const stats = tracker.getStats();
expect(stats.avgTokensPerObservation).toBe(15); // (10 + 20) / 2
});
});
describe('top/low value entries', () => {
beforeEach(() => {
// Create entries with different value scores
tracker.recordCreation('high-value', 'A'.repeat(200));
tracker.recordRetrieval('high-value', 'B'.repeat(40));
tracker.recordRetrieval('high-value', 'B'.repeat(40));
tracker.recordRetrieval('high-value', 'B'.repeat(40));
tracker.recordCreation('low-value', 'A'.repeat(40));
// No retrievals - never accessed
tracker.recordCreation('medium-value', 'A'.repeat(100));
tracker.recordRetrieval('medium-value', 'B'.repeat(40));
});
it('should return top value entries', () => {
const top = tracker.getTopValueEntries(2);
expect(top.length).toBe(2);
expect(top[0].entryId).toBe('high-value');
});
it('should return low value entries', () => {
const low = tracker.getLowValueEntries(2);
expect(low.length).toBeGreaterThanOrEqual(1);
// low-value has 0 access count
expect(low.some((e) => e.entryId === 'low-value')).toBe(true);
});
});
describe('session tracking', () => {
it('should track session input tokens', () => {
tracker.recordSessionInput('session1', 500);
const summary = tracker.getSessionSummary('session1');
expect(summary).toBeDefined();
expect(summary?.inputTokens).toBe(500);
});
it('should track session recalls', () => {
tracker.recordCreation('entry1', 'Test content');
tracker.recordRetrieval('entry1', 'Test content', 'session1');
const summary = tracker.getSessionSummary('session1');
expect(summary).toBeDefined();
expect(summary?.memoriesRecalled).toBe(1);
});
it('should get all session summaries', () => {
tracker.recordSessionInput('session1', 100);
tracker.recordSessionInput('session2', 200);
const summaries = tracker.getAllSessionSummaries();
expect(summaries.length).toBe(2);
});
});
describe('export/import', () => {
it('should export metrics', () => {
tracker.recordCreation('entry1', 'Test content');
tracker.recordRetrieval('entry1', 'Test content');
const exported = tracker.export();
expect(exported.stats).toBeDefined();
expect(exported.entries).toBeDefined();
expect(exported.entries.length).toBe(1);
});
it('should import metrics', () => {
const data = {
entries: [
{
entryId: 'imported1',
discoveryTokens: 100,
readTokens: 50,
savings: 50,
accessCount: 5,
valueScore: 250,
},
],
};
tracker.import(data);
const metrics = tracker.getEntryMetrics('imported1');
expect(metrics.length).toBe(1);
expect(metrics[0].discoveryTokens).toBe(100);
});
});
describe('reset', () => {
it('should reset all metrics', () => {
tracker.recordCreation('entry1', 'Test content');
tracker.recordRetrieval('entry1', 'Test content');
tracker.reset();
const stats = tracker.getStats();
expect(stats.totalObservations).toBe(0);
expect(stats.totalDiscoveryTokens).toBe(0);
expect(stats.totalReadTokens).toBe(0);
});
});
describe('formatStats', () => {
it('should format stats for display', () => {
tracker.recordCreation('entry1', 'Test content for display');
const formatted = tracker.formatStats();
expect(formatted).toContain('Token Economics');
expect(formatted).toContain('Observations');
expect(formatted).toContain('Discovery Tokens');
});
});
describe('createTokenEconomicsTracker factory', () => {
it('should create tracker with default config', () => {
const tracker = createTokenEconomicsTracker();
expect(tracker).toBeInstanceOf(TokenEconomicsTracker);
});
it('should create tracker with custom config', () => {
const tracker = createTokenEconomicsTracker({ charsPerToken: 5 });
// 10 chars / 5 = 2 tokens
expect(tracker.estimateTokens('1234567890')).toBe(2);
});
});
describe('configuration', () => {
it('should use default chars per token', () => {
const tracker = new TokenEconomicsTracker();
expect(tracker.estimateTokens('1234')).toBe(1);
});
it('should accept custom chars per token', () => {
const tracker = new TokenEconomicsTracker({ charsPerToken: 2 });
expect(tracker.estimateTokens('1234')).toBe(2);
});
it('should include metadata tokens when configured', () => {
const tracker = new TokenEconomicsTracker({ includeMetadata: true });
tracker.recordCreation('entry1', 'content', { key: 'value' });
const metrics = tracker.getEntryMetrics('entry1');
// Should include metadata tokens
expect(metrics[0].discoveryTokens).toBeGreaterThan(
Math.ceil('content'.length / 4)
);
});
});
});