mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-08 10:27:46 +08:00
Add multi-database data layer
Introduce aether-data-schema and driver-specific schema generation for Postgres, MySQL, and SQLite. Split data backends, lifecycle, repositories, and gateway runtime integration across database drivers. Verified with cargo fmt --all --check, cargo clippy --workspace --all-targets -- -D warnings, and cargo test --workspace.
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const {
|
||||
getAllUsageRecordsMock,
|
||||
getUsageStatsMock,
|
||||
getUsageByModelMock,
|
||||
getUsageByProviderMock,
|
||||
getUsageByApiFormatMock,
|
||||
meGetUsageMock,
|
||||
} = vi.hoisted(() => ({
|
||||
getAllUsageRecordsMock: vi.fn(),
|
||||
getUsageStatsMock: vi.fn(),
|
||||
getUsageByModelMock: vi.fn(),
|
||||
getUsageByProviderMock: vi.fn(),
|
||||
getUsageByApiFormatMock: vi.fn(),
|
||||
meGetUsageMock: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/api/usage', () => ({
|
||||
usageApi: {
|
||||
getAllUsageRecords: getAllUsageRecordsMock,
|
||||
getUsageStats: getUsageStatsMock,
|
||||
getUsageByModel: getUsageByModelMock,
|
||||
getUsageByProvider: getUsageByProviderMock,
|
||||
getUsageByApiFormat: getUsageByApiFormatMock,
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/api/me', () => ({
|
||||
meApi: {
|
||||
getUsage: meGetUsageMock,
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/utils/logger', () => ({
|
||||
log: {
|
||||
debug: vi.fn(),
|
||||
error: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
http: vi.fn(),
|
||||
performance: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
import { useUsageData } from '../useUsageData'
|
||||
import type { UsageRecord } from '../../types'
|
||||
|
||||
function buildUsageRecord(overrides: Partial<UsageRecord> = {}): UsageRecord {
|
||||
return {
|
||||
id: 'usage-1',
|
||||
model: 'gpt-5',
|
||||
input_tokens: 10,
|
||||
output_tokens: 5,
|
||||
total_tokens: 15,
|
||||
cost: 0.01,
|
||||
is_stream: false,
|
||||
created_at: '2026-05-01T00:00:00Z',
|
||||
status: 'completed',
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
describe('useUsageData', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
|
||||
getAllUsageRecordsMock.mockResolvedValue({
|
||||
records: [buildUsageRecord()],
|
||||
total: 1,
|
||||
limit: 20,
|
||||
offset: 0,
|
||||
})
|
||||
getUsageStatsMock.mockRejectedValue({
|
||||
response: { status: 500 },
|
||||
message: 'stats failed',
|
||||
})
|
||||
getUsageByModelMock.mockResolvedValue([])
|
||||
getUsageByProviderMock.mockResolvedValue([])
|
||||
getUsageByApiFormatMock.mockResolvedValue([])
|
||||
meGetUsageMock.mockResolvedValue({})
|
||||
})
|
||||
|
||||
it('keeps admin records when stats refresh fails', async () => {
|
||||
const isAdminPage = ref(true)
|
||||
const { loadRecords, loadStats, currentRecords, totalRecords } = useUsageData({ isAdminPage })
|
||||
const dateRange = { preset: 'last7days', tz_offset_minutes: 0 }
|
||||
|
||||
await loadRecords({ page: 1, pageSize: 20 }, undefined, dateRange)
|
||||
|
||||
expect(currentRecords.value).toHaveLength(1)
|
||||
expect(totalRecords.value).toBe(1)
|
||||
|
||||
await loadStats(dateRange)
|
||||
|
||||
expect(currentRecords.value).toHaveLength(1)
|
||||
expect(currentRecords.value[0]).toMatchObject({
|
||||
id: 'usage-1',
|
||||
model: 'gpt-5',
|
||||
})
|
||||
expect(totalRecords.value).toBe(1)
|
||||
})
|
||||
|
||||
it('continues loading admin breakdowns when the summary request fails', async () => {
|
||||
const isAdminPage = ref(true)
|
||||
const {
|
||||
loadStats,
|
||||
stats,
|
||||
modelStats,
|
||||
providerStats,
|
||||
apiFormatStats,
|
||||
availableModels,
|
||||
availableProviders,
|
||||
} = useUsageData({ isAdminPage })
|
||||
const dateRange = { preset: 'last7days', tz_offset_minutes: 0 }
|
||||
|
||||
getUsageStatsMock.mockRejectedValueOnce({
|
||||
response: { status: 500 },
|
||||
message: 'summary failed',
|
||||
})
|
||||
getUsageByModelMock.mockResolvedValueOnce([
|
||||
{
|
||||
model: 'gpt-5',
|
||||
request_count: 3,
|
||||
total_tokens: 300,
|
||||
total_cost: 1.23,
|
||||
},
|
||||
])
|
||||
getUsageByProviderMock.mockResolvedValueOnce([
|
||||
{
|
||||
provider: 'OpenAI',
|
||||
request_count: 3,
|
||||
total_tokens: 300,
|
||||
total_cost: 1.23,
|
||||
actual_cost: 1.5,
|
||||
avg_response_time_ms: 1250,
|
||||
success_rate: 1,
|
||||
},
|
||||
])
|
||||
getUsageByApiFormatMock.mockResolvedValueOnce([
|
||||
{
|
||||
api_format: 'openai:chat',
|
||||
request_count: 3,
|
||||
total_tokens: 300,
|
||||
total_cost: 1.23,
|
||||
actual_cost: 1.5,
|
||||
avg_response_time_ms: 1250,
|
||||
},
|
||||
])
|
||||
|
||||
const hadFailure = await loadStats(dateRange)
|
||||
|
||||
expect(hadFailure).toBe(true)
|
||||
expect(stats.value).toMatchObject({
|
||||
total_requests: 0,
|
||||
total_tokens: 0,
|
||||
total_cost: 0,
|
||||
})
|
||||
expect(modelStats.value).toHaveLength(1)
|
||||
expect(providerStats.value).toHaveLength(1)
|
||||
expect(apiFormatStats.value).toHaveLength(1)
|
||||
expect(availableModels.value).toEqual(['gpt-5'])
|
||||
expect(availableProviders.value).toEqual(['OpenAI'])
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user