mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-08 18:37:46 +08:00
refactor(workspace): enforce layered crate boundaries
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const { getMock, cachedRequestMock, buildCacheKeyMock } = vi.hoisted(() => ({
|
||||
getMock: vi.fn(),
|
||||
cachedRequestMock: vi.fn(async (_key: string, fetcher: () => Promise<unknown>) => fetcher()),
|
||||
buildCacheKeyMock: vi.fn((prefix: string) => prefix),
|
||||
}))
|
||||
|
||||
vi.mock('@/api/client', () => ({
|
||||
default: {
|
||||
get: getMock,
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/utils/cache', () => ({
|
||||
cache: {
|
||||
clear: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
},
|
||||
cachedRequest: cachedRequestMock,
|
||||
buildCacheKey: buildCacheKeyMock,
|
||||
}))
|
||||
|
||||
import { adminApi } from '@/api/admin'
|
||||
|
||||
describe('adminApi analytics cache options', () => {
|
||||
const params = {
|
||||
start_date: '2026-07-01',
|
||||
end_date: '2026-07-15',
|
||||
preset: 'custom',
|
||||
timezone: 'Asia/Shanghai',
|
||||
tz_offset_minutes: 480,
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
getMock.mockReset()
|
||||
getMock.mockResolvedValue({ data: {} })
|
||||
cachedRequestMock.mockClear()
|
||||
buildCacheKeyMock.mockClear()
|
||||
})
|
||||
|
||||
it('keeps the existing 20-second cache TTL by default', async () => {
|
||||
await adminApi.getTimeSeries(params)
|
||||
await adminApi.getPercentiles(params)
|
||||
await adminApi.getProviderPerformance(params)
|
||||
await adminApi.getErrorDistribution(params)
|
||||
|
||||
for (let call = 1; call <= 4; call += 1) {
|
||||
expect(cachedRequestMock).toHaveBeenNthCalledWith(
|
||||
call,
|
||||
expect.any(String),
|
||||
expect.any(Function),
|
||||
20 * 1000
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('uses a zero TTL when an analytics request skips the cache', async () => {
|
||||
const options = { skipCache: true }
|
||||
const providerParams = { ...params, include_timeline: false }
|
||||
|
||||
await adminApi.getTimeSeries(params, options)
|
||||
await adminApi.getPercentiles(params, options)
|
||||
await adminApi.getProviderPerformance(providerParams, options)
|
||||
await adminApi.getErrorDistribution(params, options)
|
||||
|
||||
for (let call = 1; call <= 4; call += 1) {
|
||||
expect(cachedRequestMock).toHaveBeenNthCalledWith(
|
||||
call,
|
||||
expect.any(String),
|
||||
expect.any(Function),
|
||||
0
|
||||
)
|
||||
}
|
||||
|
||||
expect(getMock).toHaveBeenNthCalledWith(1, '/api/admin/stats/time-series', { params })
|
||||
expect(getMock).toHaveBeenNthCalledWith(2, '/api/admin/stats/performance/percentiles', {
|
||||
params,
|
||||
})
|
||||
expect(getMock).toHaveBeenNthCalledWith(3, '/api/admin/stats/performance/providers', {
|
||||
params: providerParams,
|
||||
})
|
||||
expect(getMock).toHaveBeenNthCalledWith(4, '/api/admin/stats/errors/distribution', { params })
|
||||
})
|
||||
})
|
||||
@@ -2,6 +2,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { AxiosAdapter, AxiosInstance, InternalAxiosRequestConfig } from 'axios'
|
||||
|
||||
import apiClient, { AUTH_STATE_CHANGE_EVENT } from '@/api/client'
|
||||
import { cache, cachedRequest } from '@/utils/cache'
|
||||
|
||||
type TestableApiClient = typeof apiClient & {
|
||||
client: AxiosInstance
|
||||
@@ -34,6 +35,42 @@ describe('apiClient auth state change event', () => {
|
||||
window.removeEventListener(AUTH_STATE_CHANGE_EVENT, handler as EventListener)
|
||||
})
|
||||
|
||||
it('clears cached API data whenever the authentication identity changes', () => {
|
||||
apiClient.setToken('first-token')
|
||||
cache.set('dashboard', { owner: 'first-user' }, 30_000)
|
||||
|
||||
apiClient.setToken('second-token')
|
||||
|
||||
expect(cache.get('dashboard')).toBeNull()
|
||||
})
|
||||
|
||||
it('does not share or restore an in-flight cached response across token changes', async () => {
|
||||
let resolveFirst!: (value: string) => void
|
||||
let resolveSecond!: (value: string) => void
|
||||
const firstResponse = new Promise<string>((resolve) => {
|
||||
resolveFirst = resolve
|
||||
})
|
||||
const secondResponse = new Promise<string>((resolve) => {
|
||||
resolveSecond = resolve
|
||||
})
|
||||
const secondFetcher = vi.fn(() => secondResponse)
|
||||
|
||||
apiClient.setToken('first-token')
|
||||
const firstRequest = cachedRequest('dashboard', () => firstResponse, 30_000)
|
||||
|
||||
apiClient.setToken('second-token')
|
||||
const secondRequest = cachedRequest('dashboard', secondFetcher, 30_000)
|
||||
expect(secondFetcher).toHaveBeenCalledTimes(1)
|
||||
|
||||
resolveFirst('first-user-data')
|
||||
await expect(firstRequest).resolves.toBe('first-user-data')
|
||||
expect(cache.get('dashboard')).toBeNull()
|
||||
|
||||
resolveSecond('second-user-data')
|
||||
await expect(secondRequest).resolves.toBe('second-user-data')
|
||||
expect(cache.get('dashboard')).toBe('second-user-data')
|
||||
})
|
||||
|
||||
it('sends auth refresh without a request body', async () => {
|
||||
const rawClient = apiClient as TestableApiClient
|
||||
const previousAdapter = rawClient.client.defaults.adapter
|
||||
|
||||
Reference in New Issue
Block a user