Files
metapi/src/server/services/tokenRouter.session-decoupling.test.ts
T
cita ed05b37626 feat: enhance TokenRoutes with edit functionality and route visibility management
- Added editing capabilities for route management in TokenRoutes, allowing users to modify existing routes.
- Introduced a reset function for the route form to clear inputs after adding or editing routes.
- Implemented a visibility filter for routes, ensuring exact routes covered by group routes are hidden from the main list.
- Updated tests to cover new functionalities, including editing routes and verifying visibility logic.
- Created a new helper function to build a list of visible routes based on defined criteria.
- Enhanced schema compatibility checks for site configurations, ensuring necessary columns are added to the database.
2026-03-07 19:56:40 +08:00

139 lines
4.6 KiB
TypeScript

import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest';
import { mkdtempSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
type DbModule = typeof import('../db/index.js');
type TokenRouterModule = typeof import('./tokenRouter.js');
describe('TokenRouter session decoupling', () => {
let db: DbModule['db'];
let schema: DbModule['schema'];
let TokenRouter: TokenRouterModule['TokenRouter'];
let invalidateTokenRouterCache: TokenRouterModule['invalidateTokenRouterCache'];
let dataDir = '';
beforeAll(async () => {
dataDir = mkdtempSync(join(tmpdir(), 'metapi-token-router-session-decoupling-'));
process.env.DATA_DIR = dataDir;
await import('../db/migrate.js');
const dbModule = await import('../db/index.js');
const tokenRouterModule = await import('./tokenRouter.js');
db = dbModule.db;
schema = dbModule.schema;
TokenRouter = tokenRouterModule.TokenRouter;
invalidateTokenRouterCache = tokenRouterModule.invalidateTokenRouterCache;
});
beforeEach(async () => {
await db.delete(schema.routeChannels).run();
await db.delete(schema.tokenRoutes).run();
await db.delete(schema.tokenModelAvailability).run();
await db.delete(schema.modelAvailability).run();
await db.delete(schema.accountTokens).run();
await db.delete(schema.accounts).run();
await db.delete(schema.sites).run();
invalidateTokenRouterCache();
});
afterAll(() => {
invalidateTokenRouterCache();
delete process.env.DATA_DIR;
});
it('keeps explicit token-bound channels routable when account session is expired', async () => {
const site = await db.insert(schema.sites).values({
name: 'managed-site',
url: 'https://managed.example.com',
platform: 'new-api',
status: 'active',
}).returning().get();
const account = await db.insert(schema.accounts).values({
siteId: site.id,
username: 'expired-session-user',
accessToken: 'expired-session-token',
apiToken: null,
status: 'expired',
}).returning().get();
const token = await db.insert(schema.accountTokens).values({
accountId: account.id,
name: 'stable-token',
token: 'sk-stable-token',
enabled: true,
isDefault: true,
}).returning().get();
const route = await db.insert(schema.tokenRoutes).values({
modelPattern: 'gpt-4o-mini',
enabled: true,
}).returning().get();
const channel = await db.insert(schema.routeChannels).values({
routeId: route.id,
accountId: account.id,
tokenId: token.id,
priority: 0,
weight: 10,
enabled: true,
manualOverride: false,
}).returning().get();
const router = new TokenRouter();
const selected = await router.selectChannel('gpt-4o-mini');
expect(selected).not.toBeNull();
expect(selected?.channel.id).toBe(channel.id);
expect(selected?.tokenValue).toBe('sk-stable-token');
expect(selected?.account.id).toBe(account.id);
const decision = await router.explainSelection('gpt-4o-mini');
const candidate = decision.candidates.find((item) => item.channelId === channel.id);
expect(candidate?.eligible).toBe(true);
expect(candidate?.reason).not.toContain('账号状态=expired');
});
it('still blocks fallback account-token channels when account session is expired', async () => {
const site = await db.insert(schema.sites).values({
name: 'legacy-site',
url: 'https://legacy.example.com',
platform: 'new-api',
status: 'active',
}).returning().get();
const account = await db.insert(schema.accounts).values({
siteId: site.id,
username: 'legacy-user',
accessToken: 'expired-session-token',
apiToken: 'sk-fallback-account-token',
status: 'expired',
}).returning().get();
const route = await db.insert(schema.tokenRoutes).values({
modelPattern: 'gpt-4.1-mini',
enabled: true,
}).returning().get();
const channel = await db.insert(schema.routeChannels).values({
routeId: route.id,
accountId: account.id,
tokenId: null,
priority: 0,
weight: 10,
enabled: true,
manualOverride: false,
}).returning().get();
const router = new TokenRouter();
const selected = await router.selectChannel('gpt-4.1-mini');
expect(selected).toBeNull();
const decision = await router.explainSelection('gpt-4.1-mini');
const candidate = decision.candidates.find((item) => item.channelId === channel.id);
expect(candidate?.eligible).toBe(false);
expect(candidate?.reason).toContain('账号状态=expired');
});
});