import { describe, expect, test, vi } from 'vitest'; import { createAgcAdminWebEndpoint, createAgcDevEndpoint, readConfiguredAgcAdminWebPort, resolveAgcAdminWebEndpoint, resolveAgcDevEndpoint, withAgcDevEndpointEnv, } from '../scripts/dev-port.mjs'; describe('AI 游戏创作 dev 端口', () => { test('Linux 使用用户端口段的 start + 5 槽位', async () => { const findPort = vi.fn(async ({ preferredPort }) => preferredPort); const endpoint = await resolveAgcDevEndpoint({ env: { USER: 'alice', LOGNAME: 'alice' }, platform: 'linux', reservePortRange: async () => ({ username: 'alice', range: { start: 10000, end: 10099, label: '10000-10099' }, }), findPort, }); expect(endpoint).toMatchObject({ port: 10005, url: 'http://127.0.0.1:10005/', markerUrl: 'http://127.0.0.1:10005/__agc_dev_server.json', }); expect(findPort).toHaveBeenCalledWith( expect.objectContaining({ preferredPort: 10005, portRange: { start: 10000, end: 10099, label: '10000-10099' }, strict: false, }), ); }); test('Linux 槽位被占时接受本用户端口段内的漂移结果', async () => { const endpoint = await resolveAgcDevEndpoint({ env: { USER: 'alice', LOGNAME: 'alice' }, platform: 'linux', reservePortRange: async () => ({ username: 'alice', range: { start: 10000, end: 10099, label: '10000-10099' }, }), findPort: async ({ preferredPort, portRange }) => { expect(preferredPort).toBe(10005); expect(portRange?.label).toBe('10000-10099'); return 10006; }, }); expect(endpoint.port).toBe(10006); }); test('父启动器选定端口后子启动器严格使用同一端口', async () => { const findPort = vi.fn(async ({ preferredPort }) => preferredPort); await resolveAgcDevEndpoint({ env: { USER: 'alice', LOGNAME: 'alice', GENARRATIVE_AGC_VITE_PORT: '10007', }, platform: 'linux', strictConfigured: true, reservePortRange: async () => ({ username: 'alice', range: { start: 10000, end: 10099, label: '10000-10099' }, }), findPort, }); expect(findPort).toHaveBeenCalledWith( expect.objectContaining({ preferredPort: 10007, strict: true }), ); }); test('旧五端口段无法安全扩容时 AGC 明确失败而不越界', async () => { await expect( resolveAgcDevEndpoint({ env: { USER: 'alice', LOGNAME: 'alice' }, platform: 'linux', reservePortRange: async () => ({ username: 'alice', range: { start: 10000, end: 10004, label: '10000-10004' }, }), findPort: async () => 10005, }), ).rejects.toThrow('缺少 AGC Vite 槽位'); }); test('Linux 显式 AGC 端口不能占用前五个服务槽位', async () => { await expect( resolveAgcDevEndpoint({ env: { USER: 'alice', LOGNAME: 'alice', GENARRATIVE_AGC_VITE_PORT: '10004', }, platform: 'linux', reservePortRange: async () => ({ username: 'alice', range: { start: 10000, end: 10099, label: '10000-10099' }, }), findPort: async () => 10004, }), ).rejects.toThrow('前五个服务槽位'); }); test('非 Linux 保留 3080 兼容优先端口并允许统一漂移', async () => { const findPort = vi.fn(async () => 3081); const endpoint = await resolveAgcDevEndpoint({ env: {}, platform: 'win32', findPort, }); expect(endpoint.port).toBe(3081); expect(findPort).toHaveBeenCalledWith( expect.objectContaining({ preferredPort: 3080, portRange: null }), ); }); test('最终端口通过单一环境变量传给 Tauri 和 Vite 子进程', () => { expect( withAgcDevEndpointEnv(createAgcDevEndpoint(10008), { KEEP_ME: 'yes' }), ).toMatchObject({ KEEP_ME: 'yes', GENARRATIVE_AGC_VITE_PORT: '10008', }); }); }); describe('AGC 开发态后台 Web 端口', () => { test('Linux 使用用户端口段的 start + 3 槽位', async () => { const findPort = vi.fn(async ({ preferredPort }) => preferredPort); const endpoint = await resolveAgcAdminWebEndpoint({ env: { USER: 'alice', LOGNAME: 'alice' }, platform: 'linux', reservePortRange: async () => ({ username: 'alice', range: { start: 10000, end: 10099, label: '10000-10099' }, }), findPort, }); expect(endpoint).toMatchObject({ port: 10003, origin: 'http://127.0.0.1:10003', basePath: '/admin/', url: 'http://127.0.0.1:10003/admin/', }); expect(findPort).toHaveBeenCalledWith( expect.objectContaining({ preferredPort: 10003, portRange: { start: 10000, end: 10099, label: '10000-10099' }, strict: false, }), ); }); test('AGC Vite 端口作为保留端口传入,避免被后台 Web 抢先占用', async () => { const findPort = vi.fn(async ({ preferredPort }) => preferredPort); await resolveAgcAdminWebEndpoint({ env: {}, platform: 'win32', reservedPorts: [10005, 0, null as unknown as number], findPort, }); const [call] = findPort.mock.calls; expect([...call[0].reservedPorts]).toEqual([10005]); }); test('非 Linux 保留 3102 兼容优先端口并允许统一漂移', async () => { const findPort = vi.fn(async ({ preferredPort }) => preferredPort + 1); const endpoint = await resolveAgcAdminWebEndpoint({ env: {}, platform: 'win32', findPort, }); expect(endpoint.port).toBe(3103); expect(endpoint.url).toBe('http://127.0.0.1:3103/admin/'); expect(findPort).toHaveBeenCalledWith( expect.objectContaining({ preferredPort: 3102, portRange: null }), ); }); test('显式 ADMIN_WEB_PORT 与脚本统一命名并支持严格占用', async () => { const findPort = vi.fn(async ({ preferredPort }) => preferredPort); await resolveAgcAdminWebEndpoint({ env: { ADMIN_WEB_PORT: '3109' }, platform: 'win32', strictConfigured: true, findPort, }); expect(findPort).toHaveBeenCalledWith( expect.objectContaining({ preferredPort: 3109, strict: true }), ); expect(readConfiguredAgcAdminWebPort({ ADMIN_WEB_PORT: '3109' })).toBe( 3109, ); expect(readConfiguredAgcAdminWebPort({})).toBeNull(); }); test('非法 ADMIN_WEB_PORT 直接失败而不是静默回退', () => { expect(() => readConfiguredAgcAdminWebPort({ ADMIN_WEB_PORT: '80' }), ).toThrow('ADMIN_WEB_PORT 必须是 1024-65535 的有效端口'); expect(() => readConfiguredAgcAdminWebPort({ ADMIN_WEB_PORT: 'not-a-port' }), ).toThrow('ADMIN_WEB_PORT 必须是 1024-65535 的有效端口'); }); test('后台 Web 地址固定带 /admin/ base', () => { expect(createAgcAdminWebEndpoint(3102, null)).toMatchObject({ host: '127.0.0.1', port: 3102, url: 'http://127.0.0.1:3102/admin/', }); }); });