feat(tests): add comprehensive tests for TokenRouter and related components

- Implement tests for TokenRouter downstream policy, covering allowedRouteIds and site weight multipliers.
- Add tests for TokenRouter patterns and model mapping, including regex pattern matching and model mapping precedence.
- Create tests for BrandIcon component to verify brand detection logic.
- Develop tests for Accounts page, focusing on rebind panel behavior and refresh action on error.
- Introduce fuzzy search utility tests to validate substring and subsequence matching.
- Implement navigation focus helpers tests for building and parsing focus paths.
- Add progressive render tests to ensure correct initial and next visible counts.
- Create route model candidates index tests to validate route candidate indexing and matching logic.
This commit is contained in:
cita
2026-03-02 13:33:35 +08:00
parent 99c9e145c3
commit f6389a999a
98 changed files with 11286 additions and 801 deletions
+71
View File
@@ -113,8 +113,79 @@ function ensureSiteStatusSchema() {
`);
}
function ensureSiteProxySchema() {
if (!tableExists('sites')) {
return;
}
if (!tableColumnExists('sites', 'proxy_url')) {
sqlite.exec(`ALTER TABLE sites ADD COLUMN proxy_url text;`);
}
}
function ensureRouteGroupingSchema() {
if (!tableExists('token_routes') || !tableExists('route_channels')) {
return;
}
if (!tableColumnExists('token_routes', 'display_name')) {
sqlite.exec(`ALTER TABLE token_routes ADD COLUMN display_name text;`);
}
if (!tableColumnExists('token_routes', 'display_icon')) {
sqlite.exec(`ALTER TABLE token_routes ADD COLUMN display_icon text;`);
}
if (!tableColumnExists('route_channels', 'source_model')) {
sqlite.exec(`ALTER TABLE route_channels ADD COLUMN source_model text;`);
}
}
function ensureDownstreamApiKeySchema() {
sqlite.exec(`
CREATE TABLE IF NOT EXISTS downstream_api_keys (
id integer PRIMARY KEY AUTOINCREMENT NOT NULL,
name text NOT NULL,
key text NOT NULL,
description text,
enabled integer DEFAULT true,
expires_at text,
max_cost real,
used_cost real DEFAULT 0,
max_requests integer,
used_requests integer DEFAULT 0,
supported_models text,
allowed_route_ids text,
site_weight_multipliers text,
last_used_at text,
created_at text DEFAULT (datetime('now')),
updated_at text DEFAULT (datetime('now'))
);
`);
sqlite.exec(`
CREATE UNIQUE INDEX IF NOT EXISTS downstream_api_keys_key_unique
ON downstream_api_keys(key);
`);
sqlite.exec(`
CREATE INDEX IF NOT EXISTS downstream_api_keys_name_idx
ON downstream_api_keys(name);
`);
sqlite.exec(`
CREATE INDEX IF NOT EXISTS downstream_api_keys_enabled_idx
ON downstream_api_keys(enabled);
`);
sqlite.exec(`
CREATE INDEX IF NOT EXISTS downstream_api_keys_expires_at_idx
ON downstream_api_keys(expires_at);
`);
}
ensureTokenManagementSchema();
ensureSiteStatusSchema();
ensureSiteProxySchema();
ensureRouteGroupingSchema();
ensureDownstreamApiKeySchema();
export const db = drizzle(sqlite, { schema });
export { schema };
+29 -1
View File
@@ -5,7 +5,8 @@ export const sites = sqliteTable('sites', {
id: integer('id').primaryKey({ autoIncrement: true }),
name: text('name').notNull(),
url: text('url').notNull(),
platform: text('platform').notNull(), // 'new-api' | 'one-api' | 'veloera' | 'one-hub' | 'done-hub' | 'sub2api'
platform: text('platform').notNull(), // 'new-api' | 'one-api' | 'veloera' | 'one-hub' | 'done-hub' | 'sub2api' | 'openai' | 'claude' | 'gemini'
proxyUrl: text('proxy_url'),
status: text('status').notNull().default('active'), // 'active' | 'disabled'
isPinned: integer('is_pinned', { mode: 'boolean' }).default(false),
sortOrder: integer('sort_order').default(0),
@@ -101,6 +102,8 @@ export const tokenModelAvailability = sqliteTable('token_model_availability', {
export const tokenRoutes = sqliteTable('token_routes', {
id: integer('id').primaryKey({ autoIncrement: true }),
modelPattern: text('model_pattern').notNull(),
displayName: text('display_name'),
displayIcon: text('display_icon'),
modelMapping: text('model_mapping'), // JSON
enabled: integer('enabled', { mode: 'boolean' }).default(true),
createdAt: text('created_at').default(sql`(datetime('now'))`),
@@ -115,6 +118,7 @@ export const routeChannels = sqliteTable('route_channels', {
routeId: integer('route_id').notNull().references(() => tokenRoutes.id, { onDelete: 'cascade' }),
accountId: integer('account_id').notNull().references(() => accounts.id, { onDelete: 'cascade' }),
tokenId: integer('token_id').references(() => accountTokens.id, { onDelete: 'set null' }),
sourceModel: text('source_model'),
priority: integer('priority').default(0),
weight: integer('weight').default(10),
enabled: integer('enabled', { mode: 'boolean' }).default(true),
@@ -163,6 +167,30 @@ export const settings = sqliteTable('settings', {
value: text('value'), // JSON
});
export const downstreamApiKeys = sqliteTable('downstream_api_keys', {
id: integer('id').primaryKey({ autoIncrement: true }),
name: text('name').notNull(),
key: text('key').notNull(),
description: text('description'),
enabled: integer('enabled', { mode: 'boolean' }).default(true),
expiresAt: text('expires_at'),
maxCost: real('max_cost'),
usedCost: real('used_cost').default(0),
maxRequests: integer('max_requests'),
usedRequests: integer('used_requests').default(0),
supportedModels: text('supported_models'), // JSON array<string>
allowedRouteIds: text('allowed_route_ids'), // JSON array<number>
siteWeightMultipliers: text('site_weight_multipliers'), // JSON object { [siteId]: multiplier }
lastUsedAt: text('last_used_at'),
createdAt: text('created_at').default(sql`(datetime('now'))`),
updatedAt: text('updated_at').default(sql`(datetime('now'))`),
}, (table) => ({
keyUnique: uniqueIndex('downstream_api_keys_key_unique').on(table.key),
nameIdx: index('downstream_api_keys_name_idx').on(table.name),
enabledIdx: index('downstream_api_keys_enabled_idx').on(table.enabled),
expiresAtIdx: index('downstream_api_keys_expires_at_idx').on(table.expiresAt),
}));
export const events = sqliteTable('events', {
id: integer('id').primaryKey({ autoIncrement: true }),
type: text('type').notNull(), // 'checkin' | 'balance' | 'token' | 'proxy' | 'status'