| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import { after, describe, test } from 'node:test'; |
| import assert from 'node:assert/strict'; |
| import { |
| getModelAccessConfig, |
| isModelAllowed, |
| setModelAccessList, |
| setModelAccessMode, |
| } from '../src/dashboard/model-access.js'; |
|
|
| const original = getModelAccessConfig(); |
| after(() => { |
| setModelAccessMode(original.mode); |
| setModelAccessList(original.list); |
| }); |
|
|
| describe('isModelAllowed thinking-variant inheritance (#103)', () => { |
| test('allowlist: base entry implies the -thinking sibling', () => { |
| setModelAccessMode('allowlist'); |
| setModelAccessList(['claude-opus-4.6']); |
| assert.equal(isModelAllowed('claude-opus-4.6').allowed, true); |
| assert.equal(isModelAllowed('claude-opus-4.6-thinking').allowed, true, |
| 'allowlisting the base must auto-allow the -thinking sibling'); |
| }); |
|
|
| test('allowlist: -thinking entry implies the base sibling', () => { |
| setModelAccessMode('allowlist'); |
| setModelAccessList(['claude-sonnet-4.6-thinking']); |
| assert.equal(isModelAllowed('claude-sonnet-4.6-thinking').allowed, true); |
| assert.equal(isModelAllowed('claude-sonnet-4.6').allowed, true); |
| }); |
|
|
| test('allowlist: unrelated suffixes (-fast / -1m / -high) are NOT inherited', () => { |
| setModelAccessMode('allowlist'); |
| setModelAccessList(['claude-opus-4.6']); |
| |
| |
| assert.equal(isModelAllowed('claude-opus-4.6-fast').allowed, false); |
| assert.equal(isModelAllowed('claude-opus-4.6-1m').allowed, false); |
| assert.equal(isModelAllowed('claude-opus-4.6-high').allowed, false); |
| }); |
|
|
| test('allowlist: empty list rejects everything (including -thinking)', () => { |
| setModelAccessMode('allowlist'); |
| setModelAccessList([]); |
| assert.equal(isModelAllowed('claude-opus-4.6').allowed, false); |
| assert.equal(isModelAllowed('claude-opus-4.6-thinking').allowed, false); |
| }); |
|
|
| test('blocklist: base entry also blocks the -thinking sibling', () => { |
| setModelAccessMode('blocklist'); |
| setModelAccessList(['claude-opus-4.6']); |
| const baseRes = isModelAllowed('claude-opus-4.6'); |
| const thinkRes = isModelAllowed('claude-opus-4.6-thinking'); |
| assert.equal(baseRes.allowed, false); |
| assert.equal(thinkRes.allowed, false, |
| 'blocking the base must auto-block the -thinking sibling'); |
| assert.match(thinkRes.reason || '', /-thinking|claude-opus-4\.6/); |
| }); |
|
|
| test('blocklist: -thinking entry also blocks the base sibling', () => { |
| setModelAccessMode('blocklist'); |
| setModelAccessList(['claude-sonnet-4.6-thinking']); |
| assert.equal(isModelAllowed('claude-sonnet-4.6-thinking').allowed, false); |
| assert.equal(isModelAllowed('claude-sonnet-4.6').allowed, false); |
| }); |
|
|
| test('blocklist: unrelated suffixes pass when only the base is blocked', () => { |
| setModelAccessMode('blocklist'); |
| setModelAccessList(['claude-opus-4.6']); |
| assert.equal(isModelAllowed('claude-opus-4.6-fast').allowed, true); |
| assert.equal(isModelAllowed('claude-opus-4.6-mini').allowed, true); |
| assert.equal(isModelAllowed('claude-opus-4.6-codex').allowed, true); |
| }); |
|
|
| test('mode=all bypasses inheritance entirely (everything allowed)', () => { |
| setModelAccessMode('all'); |
| setModelAccessList(['claude-opus-4.6']); |
| assert.equal(isModelAllowed('claude-opus-4.6-thinking').allowed, true); |
| assert.equal(isModelAllowed('any-other-model').allowed, true); |
| }); |
| }); |
|
|