feat(pro): add analytics, export, and multi-account commercial features
3 new agor-pro modules: analytics.rs (usage dashboard queries), export.rs (session/project Markdown report generation), profiles.rs (multi-account switching via accounts.json). 9 Tauri plugin commands. Frontend IPC bridge (pro-bridge.ts). 168 cargo tests, 14 commercial vitest tests.
This commit is contained in:
parent
6973c70c5a
commit
03fe2e2237
8 changed files with 805 additions and 3 deletions
|
|
@ -1,7 +1,12 @@
|
|||
// SPDX-License-Identifier: LicenseRef-Commercial
|
||||
// Commercial-only tests — excluded from community test runs.
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
|
||||
// Mock Tauri invoke for all pro bridge calls
|
||||
vi.mock('@tauri-apps/api/core', () => ({
|
||||
invoke: vi.fn(),
|
||||
}));
|
||||
|
||||
describe('Pro Edition', () => {
|
||||
it('commercial test suite is reachable', () => {
|
||||
|
|
@ -12,3 +17,119 @@ describe('Pro Edition', () => {
|
|||
expect(process.env.AGOR_EDITION).toBe('pro');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Pro Bridge Types', async () => {
|
||||
const bridge = await import('../../src/lib/commercial/pro-bridge');
|
||||
|
||||
it('exports analytics functions', () => {
|
||||
expect(typeof bridge.proAnalyticsSummary).toBe('function');
|
||||
expect(typeof bridge.proAnalyticsDaily).toBe('function');
|
||||
expect(typeof bridge.proAnalyticsModelBreakdown).toBe('function');
|
||||
});
|
||||
|
||||
it('exports export functions', () => {
|
||||
expect(typeof bridge.proExportSession).toBe('function');
|
||||
expect(typeof bridge.proExportProjectSummary).toBe('function');
|
||||
});
|
||||
|
||||
it('exports profile functions', () => {
|
||||
expect(typeof bridge.proListAccounts).toBe('function');
|
||||
expect(typeof bridge.proGetActiveAccount).toBe('function');
|
||||
expect(typeof bridge.proSetActiveAccount).toBe('function');
|
||||
});
|
||||
|
||||
it('exports status function', () => {
|
||||
expect(typeof bridge.proStatus).toBe('function');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Pro Analytics Bridge', async () => {
|
||||
const { invoke } = await import('@tauri-apps/api/core');
|
||||
const { proAnalyticsSummary, proAnalyticsDaily, proAnalyticsModelBreakdown } = await import('../../src/lib/commercial/pro-bridge');
|
||||
const mockInvoke = vi.mocked(invoke);
|
||||
|
||||
it('proAnalyticsSummary calls correct plugin command', async () => {
|
||||
mockInvoke.mockResolvedValueOnce({
|
||||
totalSessions: 5, totalCostUsd: 1.25, totalTokens: 50000,
|
||||
totalTurns: 30, totalToolCalls: 100, avgCostPerSession: 0.25,
|
||||
avgTokensPerSession: 10000, periodDays: 30,
|
||||
});
|
||||
const result = await proAnalyticsSummary('proj-1', 30);
|
||||
expect(mockInvoke).toHaveBeenCalledWith('plugin:agor-pro|pro_analytics_summary', { projectId: 'proj-1', days: 30 });
|
||||
expect(result.totalSessions).toBe(5);
|
||||
expect(result.avgCostPerSession).toBe(0.25);
|
||||
});
|
||||
|
||||
it('proAnalyticsDaily returns array of daily stats', async () => {
|
||||
mockInvoke.mockResolvedValueOnce([
|
||||
{ date: '2026-03-15', sessionCount: 2, costUsd: 0.50, tokens: 20000, turns: 10, toolCalls: 30 },
|
||||
{ date: '2026-03-16', sessionCount: 3, costUsd: 0.75, tokens: 30000, turns: 20, toolCalls: 50 },
|
||||
]);
|
||||
const result = await proAnalyticsDaily('proj-1');
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result[0].date).toBe('2026-03-15');
|
||||
});
|
||||
|
||||
it('proAnalyticsModelBreakdown returns model-level data', async () => {
|
||||
mockInvoke.mockResolvedValueOnce([
|
||||
{ model: 'opus', sessionCount: 3, totalCostUsd: 0.90, totalTokens: 40000, avgCostPerSession: 0.30 },
|
||||
]);
|
||||
const result = await proAnalyticsModelBreakdown('proj-1', 7);
|
||||
expect(result[0].model).toBe('opus');
|
||||
expect(mockInvoke).toHaveBeenCalledWith('plugin:agor-pro|pro_analytics_model_breakdown', { projectId: 'proj-1', days: 7 });
|
||||
});
|
||||
});
|
||||
|
||||
describe('Pro Export Bridge', async () => {
|
||||
const { invoke } = await import('@tauri-apps/api/core');
|
||||
const { proExportSession, proExportProjectSummary } = await import('../../src/lib/commercial/pro-bridge');
|
||||
const mockInvoke = vi.mocked(invoke);
|
||||
|
||||
it('proExportSession returns markdown report', async () => {
|
||||
mockInvoke.mockResolvedValueOnce({
|
||||
projectId: 'proj-1', sessionId: 'sess-1', markdown: '# Report',
|
||||
costUsd: 0.50, turnCount: 10, toolCallCount: 5, durationMinutes: 15.0, model: 'opus',
|
||||
});
|
||||
const result = await proExportSession('proj-1', 'sess-1');
|
||||
expect(result.markdown).toContain('# Report');
|
||||
expect(result.durationMinutes).toBe(15.0);
|
||||
});
|
||||
|
||||
it('proExportProjectSummary returns period summary', async () => {
|
||||
mockInvoke.mockResolvedValueOnce({
|
||||
projectId: 'proj-1', markdown: '# Summary', totalSessions: 10, totalCostUsd: 5.0, periodDays: 30,
|
||||
});
|
||||
const result = await proExportProjectSummary('proj-1', 30);
|
||||
expect(result.totalSessions).toBe(10);
|
||||
expect(result.periodDays).toBe(30);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Pro Profiles Bridge', async () => {
|
||||
const { invoke } = await import('@tauri-apps/api/core');
|
||||
const { proListAccounts, proGetActiveAccount, proSetActiveAccount } = await import('../../src/lib/commercial/pro-bridge');
|
||||
const mockInvoke = vi.mocked(invoke);
|
||||
|
||||
it('proListAccounts returns account list', async () => {
|
||||
mockInvoke.mockResolvedValueOnce([
|
||||
{ id: 'default', displayName: 'Default', email: null, provider: 'claude', configDir: '/home/.claude', isActive: true },
|
||||
{ id: 'work', displayName: 'Work', email: 'work@co.com', provider: 'claude', configDir: '/home/.claude-work', isActive: false },
|
||||
]);
|
||||
const result = await proListAccounts();
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result[0].isActive).toBe(true);
|
||||
});
|
||||
|
||||
it('proSetActiveAccount calls correct command', async () => {
|
||||
mockInvoke.mockResolvedValueOnce({ profileId: 'work', provider: 'claude', configDir: '/home/.claude-work' });
|
||||
const result = await proSetActiveAccount('work');
|
||||
expect(mockInvoke).toHaveBeenCalledWith('plugin:agor-pro|pro_set_active_account', { profileId: 'work' });
|
||||
expect(result.profileId).toBe('work');
|
||||
});
|
||||
|
||||
it('proGetActiveAccount returns current active', async () => {
|
||||
mockInvoke.mockResolvedValueOnce({ profileId: 'default', provider: 'claude', configDir: '/home/.claude' });
|
||||
const result = await proGetActiveAccount();
|
||||
expect(result.profileId).toBe('default');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue