Files
Cli-Proxy-API-Management-Ce…/tests/excludedModelRuleMatching.test.ts
Supra4E8C 4abd41f947 refactor: streamline excluded models management and enhance localization
- Removed deprecated localization keys related to excluded models in zh-TW.json.
- Introduced a new structure for excluded models with improved UI components.
- Refactored AuthFilesOAuthExcludedEditPage to utilize ExcludedModelsPicker for better handling of model exclusions.
- Consolidated styles in AuthFilesOAuthExcludedEditPage.module.scss by removing unused classes.
- Added comprehensive tests for excluded model rules and their matching logic.
- Deleted obsolete tests for OAuth excluded rules and replaced them with more relevant tests for the new structure.
- Ensured that the disabled state of providers is correctly managed and reflected in the UI.
2026-07-31 07:42:56 +08:00

149 lines
5.0 KiB
TypeScript

import { describe, expect, test } from 'bun:test';
import {
getModelExclusionState,
isModelExcluded,
matchedModelsByRule,
summarizeExclusion,
} from '../src/components/excludedModels/excludedModelRules';
const CATALOG = ['gpt-5-codex', 'gpt-5-mini', 'gpt-5-pro', 'claude-opus', 'gemini-3-pro'];
describe('getModelExclusionState', () => {
test('included when no rule touches the model', () => {
expect(getModelExclusionState(['claude-opus'], 'gpt-5-mini')).toEqual({ state: 'included' });
});
test('exact when only a literal rule matches', () => {
expect(getModelExclusionState(['gpt-5-mini'], 'gpt-5-mini')).toEqual({
state: 'excluded',
by: 'exact',
});
});
test('wildcard carries the responsible rule so the row can explain itself', () => {
expect(getModelExclusionState(['gpt-5-*'], 'gpt-5-mini')).toEqual({
state: 'excluded',
by: 'wildcard',
rule: 'gpt-5-*',
});
});
test('both when a model is explicitly picked AND caught by a wildcard', () => {
expect(getModelExclusionState(['gpt-5-mini', 'gpt-5-*'], 'gpt-5-mini')).toEqual({
state: 'excluded',
by: 'both',
rule: 'gpt-5-*',
});
});
test('order of the rules does not change the resolved state', () => {
expect(getModelExclusionState(['gpt-5-*', 'gpt-5-mini'], 'gpt-5-mini')).toEqual({
state: 'excluded',
by: 'both',
rule: 'gpt-5-*',
});
});
test('reports the first matching wildcard when several apply', () => {
expect(getModelExclusionState(['gpt-*', 'gpt-5-*'], 'gpt-5-mini')).toEqual({
state: 'excluded',
by: 'wildcard',
rule: 'gpt-*',
});
});
test('matching is case-insensitive in both directions', () => {
expect(getModelExclusionState(['GPT-5-MINI'], 'gpt-5-mini')).toEqual({
state: 'excluded',
by: 'exact',
});
expect(getModelExclusionState(['gpt-5-mini'], 'GPT-5-MINI')).toEqual({
state: 'excluded',
by: 'exact',
});
});
test('a blank model id is never excluded', () => {
expect(getModelExclusionState(['*-mini'], ' ')).toEqual({ state: 'included' });
});
test('isModelExcluded collapses all three excluded variants', () => {
expect(isModelExcluded(['gpt-5-mini'], 'gpt-5-mini')).toBe(true);
expect(isModelExcluded(['gpt-5-*'], 'gpt-5-mini')).toBe(true);
expect(isModelExcluded(['gpt-5-mini', 'gpt-5-*'], 'gpt-5-mini')).toBe(true);
expect(isModelExcluded(['claude-opus'], 'gpt-5-mini')).toBe(false);
});
});
describe('matchedModelsByRule', () => {
test('reports what each rule actually catches, in catalog order', () => {
expect(matchedModelsByRule(['gpt-5-*'], CATALOG)).toEqual([
{ rule: 'gpt-5-*', matched: ['gpt-5-codex', 'gpt-5-mini', 'gpt-5-pro'], matchCount: 3 },
]);
});
test('a rule matching nothing is reported with a zero count, not omitted', () => {
expect(matchedModelsByRule(['retired-*'], CATALOG)).toEqual([
{ rule: 'retired-*', matched: [], matchCount: 0 },
]);
});
test('an exact rule matches exactly its own model', () => {
expect(matchedModelsByRule(['claude-opus'], CATALOG)).toEqual([
{ rule: 'claude-opus', matched: ['claude-opus'], matchCount: 1 },
]);
});
test('overlapping rules each report the full set they catch', () => {
expect(matchedModelsByRule(['gpt-*', 'gpt-5-pro'], CATALOG)).toEqual([
{ rule: 'gpt-*', matched: ['gpt-5-codex', 'gpt-5-mini', 'gpt-5-pro'], matchCount: 3 },
{ rule: 'gpt-5-pro', matched: ['gpt-5-pro'], matchCount: 1 },
]);
});
test('preserves the given rule order and length', () => {
expect(matchedModelsByRule(['b-*', 'a-*'], CATALOG).map((s) => s.rule)).toEqual(['b-*', 'a-*']);
});
});
describe('summarizeExclusion', () => {
test('counts catalog models hit by any rule, not the rules themselves', () => {
// One rule, three models — a rule count would say 1 and the meter would lie.
expect(summarizeExclusion(['gpt-5-*'], CATALOG)).toEqual({
total: 5,
excluded: 3,
available: 2,
});
});
test('a model caught by both an exact and a wildcard rule counts once', () => {
expect(summarizeExclusion(['gpt-5-mini', 'gpt-5-*'], CATALOG)).toEqual({
total: 5,
excluded: 3,
available: 2,
});
});
test('rules that match nothing in the catalog do not inflate the count', () => {
expect(summarizeExclusion(['retired-model', 'gone-*'], CATALOG)).toEqual({
total: 5,
excluded: 0,
available: 5,
});
});
test('available is always total minus excluded', () => {
const stats = summarizeExclusion(['gpt-*', 'claude-opus'], CATALOG);
expect(stats.available).toBe(stats.total - stats.excluded);
expect(stats).toEqual({ total: 5, excluded: 4, available: 1 });
});
test('an empty catalog yields all zeroes rather than NaN', () => {
expect(summarizeExclusion(['gpt-5-*'], [])).toEqual({ total: 0, excluded: 0, available: 0 });
});
test('no rules means nothing excluded', () => {
expect(summarizeExclusion([], CATALOG)).toEqual({ total: 5, excluded: 0, available: 5 });
});
});