Skip to content

Commit a4deb27

Browse files
Copilothotlong
andcommitted
test: add tests for unified cost tracking and chart types
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent bffae6f commit a4deb27

File tree

4 files changed

+344
-11
lines changed

4 files changed

+344
-11
lines changed

packages/spec/src/ai/cost.test.ts

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { describe, it, expect } from 'vitest';
22
import {
3+
TokenUsageSchema,
4+
AIOperationCostSchema,
35
CostMetricTypeSchema,
46
BillingPeriodSchema,
57
CostEntrySchema,
@@ -14,12 +16,130 @@ import {
1416
CostOptimizationRecommendationSchema,
1517
CostReportSchema,
1618
CostQueryFiltersSchema,
19+
type TokenUsage,
20+
type AIOperationCost,
1721
type CostEntry,
1822
type BudgetLimit,
1923
type CostAlert,
2024
type CostReport,
2125
} from './cost.zod';
2226

27+
describe('TokenUsageSchema', () => {
28+
it('should accept valid token usage', () => {
29+
const usage: TokenUsage = {
30+
prompt: 1500,
31+
completion: 800,
32+
total: 2300,
33+
};
34+
expect(() => TokenUsageSchema.parse(usage)).not.toThrow();
35+
});
36+
37+
it('should require all fields', () => {
38+
expect(() => TokenUsageSchema.parse({
39+
prompt: 1500,
40+
completion: 800,
41+
// missing total
42+
})).toThrow();
43+
});
44+
45+
it('should reject negative values', () => {
46+
expect(() => TokenUsageSchema.parse({
47+
prompt: -100,
48+
completion: 800,
49+
total: 700,
50+
})).toThrow();
51+
});
52+
});
53+
54+
describe('AIOperationCostSchema', () => {
55+
it('should accept minimal AI operation cost', () => {
56+
const cost: AIOperationCost = {
57+
operationId: 'op-123',
58+
operationType: 'conversation',
59+
modelId: 'gpt-4-turbo',
60+
tokens: {
61+
prompt: 1500,
62+
completion: 800,
63+
total: 2300,
64+
},
65+
cost: 0.05,
66+
timestamp: '2024-01-15T10:00:00Z',
67+
};
68+
expect(() => AIOperationCostSchema.parse(cost)).not.toThrow();
69+
});
70+
71+
it('should accept all operation types', () => {
72+
const types = ['conversation', 'orchestration', 'prediction', 'rag', 'nlq'] as const;
73+
74+
types.forEach(type => {
75+
const cost: AIOperationCost = {
76+
operationId: 'op-123',
77+
operationType: type,
78+
modelId: 'gpt-4-turbo',
79+
tokens: {
80+
prompt: 1500,
81+
completion: 800,
82+
total: 2300,
83+
},
84+
cost: 0.05,
85+
timestamp: '2024-01-15T10:00:00Z',
86+
};
87+
expect(() => AIOperationCostSchema.parse(cost)).not.toThrow();
88+
});
89+
});
90+
91+
it('should accept AI operation cost with agent name and metadata', () => {
92+
const cost: AIOperationCost = {
93+
operationId: 'op-456',
94+
operationType: 'orchestration',
95+
agentName: 'support_agent',
96+
modelId: 'gpt-4-turbo',
97+
tokens: {
98+
prompt: 2000,
99+
completion: 1200,
100+
total: 3200,
101+
},
102+
cost: 0.08,
103+
timestamp: '2024-01-15T11:00:00Z',
104+
metadata: {
105+
workflowId: 'wf-789',
106+
recordId: 'case-123',
107+
},
108+
};
109+
expect(() => AIOperationCostSchema.parse(cost)).not.toThrow();
110+
});
111+
112+
it('should reject invalid timestamp', () => {
113+
expect(() => AIOperationCostSchema.parse({
114+
operationId: 'op-123',
115+
operationType: 'conversation',
116+
modelId: 'gpt-4-turbo',
117+
tokens: {
118+
prompt: 1500,
119+
completion: 800,
120+
total: 2300,
121+
},
122+
cost: 0.05,
123+
timestamp: 'invalid-timestamp',
124+
})).toThrow();
125+
});
126+
127+
it('should reject negative cost', () => {
128+
expect(() => AIOperationCostSchema.parse({
129+
operationId: 'op-123',
130+
operationType: 'conversation',
131+
modelId: 'gpt-4-turbo',
132+
tokens: {
133+
prompt: 1500,
134+
completion: 800,
135+
total: 2300,
136+
},
137+
cost: -0.05,
138+
timestamp: '2024-01-15T10:00:00Z',
139+
})).toThrow();
140+
});
141+
});
142+
23143
describe('CostMetricTypeSchema', () => {
24144
it('should accept all valid metric types', () => {
25145
const types = ['token', 'request', 'character', 'second', 'image', 'embedding'] as const;

packages/spec/src/ui/chart.test.ts

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
import { describe, it, expect } from 'vitest';
2+
import {
3+
ChartTypeSchema,
4+
ChartConfigSchema,
5+
type ChartType,
6+
type ChartConfig,
7+
} from './chart.zod';
8+
9+
describe('ChartTypeSchema', () => {
10+
it('should accept all comparison chart types', () => {
11+
const types = ['bar', 'horizontal-bar', 'column', 'grouped-bar', 'stacked-bar'] as const;
12+
13+
types.forEach(type => {
14+
expect(() => ChartTypeSchema.parse(type)).not.toThrow();
15+
});
16+
});
17+
18+
it('should accept all trend chart types', () => {
19+
const types = ['line', 'area', 'stacked-area', 'step-line'] as const;
20+
21+
types.forEach(type => {
22+
expect(() => ChartTypeSchema.parse(type)).not.toThrow();
23+
});
24+
});
25+
26+
it('should accept all distribution chart types', () => {
27+
const types = ['pie', 'donut', 'funnel'] as const;
28+
29+
types.forEach(type => {
30+
expect(() => ChartTypeSchema.parse(type)).not.toThrow();
31+
});
32+
});
33+
34+
it('should accept all relationship chart types', () => {
35+
const types = ['scatter', 'bubble'] as const;
36+
37+
types.forEach(type => {
38+
expect(() => ChartTypeSchema.parse(type)).not.toThrow();
39+
});
40+
});
41+
42+
it('should accept all composition chart types', () => {
43+
const types = ['treemap', 'sunburst', 'sankey'] as const;
44+
45+
types.forEach(type => {
46+
expect(() => ChartTypeSchema.parse(type)).not.toThrow();
47+
});
48+
});
49+
50+
it('should accept all performance chart types', () => {
51+
const types = ['gauge', 'metric', 'kpi'] as const;
52+
53+
types.forEach(type => {
54+
expect(() => ChartTypeSchema.parse(type)).not.toThrow();
55+
});
56+
});
57+
58+
it('should accept all geo chart types', () => {
59+
const types = ['choropleth', 'bubble-map'] as const;
60+
61+
types.forEach(type => {
62+
expect(() => ChartTypeSchema.parse(type)).not.toThrow();
63+
});
64+
});
65+
66+
it('should accept all advanced chart types', () => {
67+
const types = ['heatmap', 'radar', 'waterfall', 'box-plot', 'violin'] as const;
68+
69+
types.forEach(type => {
70+
expect(() => ChartTypeSchema.parse(type)).not.toThrow();
71+
});
72+
});
73+
74+
it('should accept all tabular chart types', () => {
75+
const types = ['table', 'pivot'] as const;
76+
77+
types.forEach(type => {
78+
expect(() => ChartTypeSchema.parse(type)).not.toThrow();
79+
});
80+
});
81+
82+
it('should reject invalid chart type', () => {
83+
expect(() => ChartTypeSchema.parse('invalid-chart')).toThrow();
84+
});
85+
});
86+
87+
describe('ChartConfigSchema', () => {
88+
it('should accept minimal chart config', () => {
89+
const config: ChartConfig = {
90+
type: 'bar',
91+
};
92+
const result = ChartConfigSchema.parse(config);
93+
expect(result.type).toBe('bar');
94+
expect(result.showLegend).toBe(true);
95+
expect(result.showDataLabels).toBe(false);
96+
});
97+
98+
it('should accept full chart config', () => {
99+
const config: ChartConfig = {
100+
type: 'line',
101+
title: 'Sales Trend',
102+
description: 'Monthly sales performance',
103+
showLegend: true,
104+
showDataLabels: true,
105+
colors: ['#FF6384', '#36A2EB', '#FFCE56'],
106+
};
107+
expect(() => ChartConfigSchema.parse(config)).not.toThrow();
108+
});
109+
110+
it('should apply default values', () => {
111+
const config: ChartConfig = {
112+
type: 'pie',
113+
title: 'Revenue by Region',
114+
};
115+
const result = ChartConfigSchema.parse(config);
116+
expect(result.showLegend).toBe(true);
117+
expect(result.showDataLabels).toBe(false);
118+
});
119+
120+
it('should allow custom colors', () => {
121+
const config: ChartConfig = {
122+
type: 'donut',
123+
colors: ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728'],
124+
};
125+
const result = ChartConfigSchema.parse(config);
126+
expect(result.colors).toHaveLength(4);
127+
});
128+
});
129+
130+
describe('Real-World Chart Configuration Examples', () => {
131+
it('should accept bar chart for comparison', () => {
132+
const config: ChartConfig = {
133+
type: 'bar',
134+
title: 'Sales by Product Category',
135+
description: 'Comparison of sales across different product categories',
136+
showLegend: true,
137+
showDataLabels: true,
138+
colors: ['#4e79a7', '#f28e2c', '#e15759'],
139+
};
140+
expect(() => ChartConfigSchema.parse(config)).not.toThrow();
141+
});
142+
143+
it('should accept line chart for trends', () => {
144+
const config: ChartConfig = {
145+
type: 'line',
146+
title: 'Revenue Trend',
147+
description: 'Monthly revenue over the past year',
148+
showLegend: true,
149+
showDataLabels: false,
150+
};
151+
expect(() => ChartConfigSchema.parse(config)).not.toThrow();
152+
});
153+
154+
it('should accept pie chart for distribution', () => {
155+
const config: ChartConfig = {
156+
type: 'pie',
157+
title: 'Market Share',
158+
description: 'Market share by competitor',
159+
showLegend: true,
160+
showDataLabels: true,
161+
};
162+
expect(() => ChartConfigSchema.parse(config)).not.toThrow();
163+
});
164+
165+
it('should accept gauge for performance metrics', () => {
166+
const config: ChartConfig = {
167+
type: 'gauge',
168+
title: 'Customer Satisfaction Score',
169+
description: 'Current satisfaction rating (0-100)',
170+
showLegend: false,
171+
colors: ['#22c55e', '#eab308', '#ef4444'],
172+
};
173+
expect(() => ChartConfigSchema.parse(config)).not.toThrow();
174+
});
175+
176+
it('should accept heatmap for correlation analysis', () => {
177+
const config: ChartConfig = {
178+
type: 'heatmap',
179+
title: 'User Activity Heatmap',
180+
description: 'Hourly user activity by day of week',
181+
showLegend: true,
182+
showDataLabels: false,
183+
colors: ['#440154', '#31688e', '#35b779', '#fde724'],
184+
};
185+
expect(() => ChartConfigSchema.parse(config)).not.toThrow();
186+
});
187+
188+
it('should accept funnel chart for conversion tracking', () => {
189+
const config: ChartConfig = {
190+
type: 'funnel',
191+
title: 'Sales Funnel',
192+
description: 'Conversion rates at each stage',
193+
showLegend: false,
194+
showDataLabels: true,
195+
};
196+
expect(() => ChartConfigSchema.parse(config)).not.toThrow();
197+
});
198+
199+
it('should accept waterfall chart for financial analysis', () => {
200+
const config: ChartConfig = {
201+
type: 'waterfall',
202+
title: 'Profit & Loss Breakdown',
203+
description: 'Revenue, costs, and profit components',
204+
showLegend: false,
205+
showDataLabels: true,
206+
colors: ['#22c55e', '#ef4444', '#6366f1'],
207+
};
208+
expect(() => ChartConfigSchema.parse(config)).not.toThrow();
209+
});
210+
});

0 commit comments

Comments
 (0)