// @vitest-environment node import { beforeEach, expect, it, vi } from 'vitest'; import { teacherModelCatalog, validateTeacherModelId } from '../../electron/coding-teacher/model-catalog'; import { TeacherError } from '../../electron/coding-teacher/config-client'; const mocks = vi.hoisted(() => ({ json: vi.fn(), request: vi.fn(), assert: vi.fn() })); vi.mock('../../electron/coding-teacher/cloud-runner', () => ({ teacherCloudTransport: () => ({ json: mocks.json }) })); vi.mock('../../electron/coding-teacher/config-client', async importOriginal => ({ ...await importOriginal(), teacherCloudRequest: mocks.request, assertTeacherAccount: mocks.assert, })); const account = { id: 'student', binding: { accountKey: 'student', epoch: 1 } }; beforeEach(() => { vi.resetAllMocks(); }); it.each([false, undefined])('does not advertise selection on an older cloud or gateway (%s)', async flag => { if (flag === undefined) mocks.json.mockRejectedValue(new TeacherError(404, 'not_found', 'Not Found')); else mocks.json.mockResolvedValue({ model_selection: flag, models: [] }); expect(await teacherModelCatalog(account, 'yuxi')).toMatchObject({ supported: false, models: [] }); expect(mocks.request).not.toHaveBeenCalled(); }); it('does not turn authentication or transient failures into unsupported capability', async () => { const error = new TeacherError(401, 'expired', '请登录'); mocks.json.mockRejectedValue(error); await expect(teacherModelCatalog(account, 'yuxi')).rejects.toBe(error); }); it.each([null, '', ' model ', 123, 'x'.repeat(201)])('rejects malformed explicit model %s', value => { expect(() => validateTeacherModelId(value)).toThrow(TeacherError); }); it('preserves absent model and exact provider ids', () => { expect(validateTeacherModelId(undefined)).toBeUndefined(); expect(validateTeacherModelId('provider/model')).toBe('provider/model'); }); it('returns only shared text models and never exposes gateway credentials', async () => { mocks.json.mockResolvedValue({ model_selection: true, models: [{ id: 'text-model' }, { id: 'image-model' }, { id: 'unlisted' }] }); mocks.request.mockResolvedValue({ api_key: 'private', base_url: 'https://private.invalid', models: ['text-model', 'image-model'], model_capabilities_v2: { schema_version: 2, models: { 'text-model': { input_modalities: ['text'], output_modalities: ['text'], reasoning: { supported: false, can_disable: false, default_enabled: false, effort_values: [], control_format: 'none' } }, 'image-model': { input_modalities: ['text'], output_modalities: ['image'], reasoning: { supported: false, can_disable: false, default_enabled: false, effort_values: [], control_format: 'none' } }, } } }); expect(await teacherModelCatalog(account, 'yuxi')).toEqual({ supported: true, models: [{ id: 'text-model' }] }); });