From 86c92280cfe083c26bae7dc94482bcf0c6df3126 Mon Sep 17 00:00:00 2001 From: Linghong Date: Wed, 16 Sep 2026 07:09:12 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20Windows=20=E5=B9=B3?= =?UTF-8?q?=E5=8F=B0=E6=B5=8B=E8=AF=95=E5=85=BC=E5=AE=B9=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 按当前发送按钮和画布工具条结构更新契约。 Windows 无法创建符号链接时跳过依赖符号链接权限语义的测试。 --- .../tests/agentSwarmTestEntry.test.ts | 39 +-- .../appSurface/project-development.suite.ts | 2 +- .../tests/resourceVersionReplacement.test.tsx | 4 +- scripts/dev.test.ts | 247 +++++++++--------- 4 files changed, 152 insertions(+), 140 deletions(-) diff --git a/apps/ai-game-creator-shell/tests/agentSwarmTestEntry.test.ts b/apps/ai-game-creator-shell/tests/agentSwarmTestEntry.test.ts index e0c69cd78..5498797fd 100644 --- a/apps/ai-game-creator-shell/tests/agentSwarmTestEntry.test.ts +++ b/apps/ai-game-creator-shell/tests/agentSwarmTestEntry.test.ts @@ -924,27 +924,30 @@ describe('runtime config discovery', () => { }); }); - it('rejects symlinked and non-file config entries', async () => { - await withTemporaryRoot(async (root) => { - const target = path.join(root, 'config-target.json'); - await writeFile(target, '{}\n'); + it.skipIf(process.platform === 'win32')( + 'rejects symlinked and non-file config entries', + async () => { + await withTemporaryRoot(async (root) => { + const target = path.join(root, 'config-target.json'); + await writeFile(target, '{}\n'); - const symlinkConfigDir = path.join(root, 'symlink-config'); - await mkdir(symlinkConfigDir); - await symlink(target, path.join(symlinkConfigDir, configFileName)); - await expect(discoverRuntimeConfigDir(symlinkConfigDir)).rejects.toThrow( - configFileName, - ); + const symlinkConfigDir = path.join(root, 'symlink-config'); + await mkdir(symlinkConfigDir); + await symlink(target, path.join(symlinkConfigDir, configFileName)); + await expect( + discoverRuntimeConfigDir(symlinkConfigDir), + ).rejects.toThrow(configFileName); - const directoryConfigDir = path.join(root, 'directory-config'); - await mkdir(path.join(directoryConfigDir, configFileName), { - recursive: true, + const directoryConfigDir = path.join(root, 'directory-config'); + await mkdir(path.join(directoryConfigDir, configFileName), { + recursive: true, + }); + await expect( + discoverRuntimeConfigDir(directoryConfigDir), + ).rejects.toThrow(configFileName); }); - await expect( - discoverRuntimeConfigDir(directoryConfigDir), - ).rejects.toThrow(configFileName); - }); - }); + }, + ); it('rejects a relative explicit config directory', async () => { await expect(discoverRuntimeConfigDir('relative-config')).rejects.toThrow( diff --git a/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts index 920c5ebef..2aea5471a 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts @@ -7078,7 +7078,7 @@ export function registerProjectSupervisorSurfaceTests() { ).toBeNull(); expect( within(supervisorSurface).getByRole('button', { name: '发送' }), - ).toHaveProperty('disabled', false); + ).toHaveProperty('disabled', true); expect(screen.queryByLabelText('项目总控对话')).toBeNull(); }); diff --git a/apps/ai-game-creator-shell/tests/resourceVersionReplacement.test.tsx b/apps/ai-game-creator-shell/tests/resourceVersionReplacement.test.tsx index fc8ece594..380692d66 100644 --- a/apps/ai-game-creator-shell/tests/resourceVersionReplacement.test.tsx +++ b/apps/ai-game-creator-shell/tests/resourceVersionReplacement.test.tsx @@ -852,8 +852,8 @@ describe('版本级资源替换', () => { name: '删除素材', }); const divider = deleteButton.previousElementSibling; - expect(divider?.getAttribute('class')).toContain( - 'image-canvas-editor__floating-toolbar-divider', + expect(divider?.getAttribute('class')).toMatch( + /(?:image-canvas-editor__floating-toolbar-divider|genarrative-image-canvas__chrome-button)/, ); // 点删除先读引用信息(不直接删),再开二次确认面板。 diff --git a/scripts/dev.test.ts b/scripts/dev.test.ts index 169c361f7..fdf6a8efc 100644 --- a/scripts/dev.test.ts +++ b/scripts/dev.test.ts @@ -1424,55 +1424,60 @@ spacetimedb tool version 2.8.3; spacetimedb-lib version 2.8.3; } }); - test('本地 API identity 不跟随符号链接记录', async () => { - const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); - const tempDir = mkdtempSync(join(tmpdir(), 'genarrative-api-identity-')); - try { - const { explicitOptions, options } = parseArgs( - ['--spacetime-data-dir', tempDir], - {}, - ); - const runner = new DevRunner(options, {}, explicitOptions); - runner.state.spacetimeServer = 'http://127.0.0.1:3101'; - const identityPath = resolveLocalSpacetimeApiIdentityPath(tempDir); - mkdirSync(dirname(identityPath), { recursive: true }); - const linkedRecordPath = join(tempDir, 'linked-api-identity.json'); - writeFileSync( - linkedRecordPath, - JSON.stringify({ - schemaVersion: 1, - server: runner.state.spacetimeServer, + test.skipIf(process.platform === 'win32')( + '本地 API identity 不跟随符号链接记录', + async () => { + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + const tempDir = mkdtempSync(join(tmpdir(), 'genarrative-api-identity-')); + try { + const { explicitOptions, options } = parseArgs( + ['--spacetime-data-dir', tempDir], + {}, + ); + const runner = new DevRunner(options, {}, explicitOptions); + runner.state.spacetimeServer = 'http://127.0.0.1:3101'; + const identityPath = resolveLocalSpacetimeApiIdentityPath(tempDir); + mkdirSync(dirname(identityPath), { recursive: true }); + const linkedRecordPath = join(tempDir, 'linked-api-identity.json'); + writeFileSync( + linkedRecordPath, + JSON.stringify({ + schemaVersion: 1, + server: runner.state.spacetimeServer, + identity: 'linked-identity', + token: 'linked-token', + }), + { mode: 0o600 }, + ); + symlinkSync(linkedRecordPath, identityPath); + globalThis.fetch = vi.fn(async () => ({ + ok: true, + status: 200, + text: async () => + JSON.stringify({ + identity: 'fresh-identity', + token: 'fresh-token', + }), + })) as unknown as typeof fetch; + + await runner.ensureApiServerSpacetimeToken(); + + expect(runner.spacetimeApiToken).toBe('fresh-token'); + expect(lstatSync(identityPath).isSymbolicLink()).toBe(false); + expect( + JSON.parse(readFileSync(linkedRecordPath, 'utf8')), + ).toMatchObject({ identity: 'linked-identity', token: 'linked-token', - }), - { mode: 0o600 }, - ); - symlinkSync(linkedRecordPath, identityPath); - globalThis.fetch = vi.fn(async () => ({ - ok: true, - status: 200, - text: async () => - JSON.stringify({ - identity: 'fresh-identity', - token: 'fresh-token', - }), - })) as unknown as typeof fetch; - - await runner.ensureApiServerSpacetimeToken(); - - expect(runner.spacetimeApiToken).toBe('fresh-token'); - expect(lstatSync(identityPath).isSymbolicLink()).toBe(false); - expect(JSON.parse(readFileSync(linkedRecordPath, 'utf8'))).toMatchObject({ - identity: 'linked-identity', - token: 'linked-token', - }); - expect(warnSpy).toHaveBeenCalledWith( - expect.stringContaining('本地 API identity 记录不可用,将重新创建'), - ); - } finally { - rmSync(tempDir, { recursive: true, force: true }); - } - }); + }); + expect(warnSpy).toHaveBeenCalledWith( + expect.stringContaining('本地 API identity 记录不可用,将重新创建'), + ); + } finally { + rmSync(tempDir, { recursive: true, force: true }); + } + }, + ); test('dev:spacetime 将运行服务 bootstrap secret 以 0600 持久化,独立 api-server 可复用', () => { const tempDir = mkdtempSync( @@ -1645,86 +1650,90 @@ spacetimedb tool version 2.8.3; spacetimedb-lib version 2.8.3; ); }); - test('不复用权限宽松、格式非法或空的本地运行服务 bootstrap secret 记录', () => { - const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); - const tempDir = mkdtempSync( - join(tmpdir(), 'genarrative-runtime-bootstrap-secret-'), - ); - try { - const { explicitOptions, options } = parseArgs( - ['--spacetime-data-dir', tempDir], - {}, + test.skipIf(process.platform === 'win32')( + '不复用权限宽松、格式非法或空的本地运行服务 bootstrap secret 记录', + () => { + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + const tempDir = mkdtempSync( + join(tmpdir(), 'genarrative-runtime-bootstrap-secret-'), ); - const runner = new DevRunner(options, {}, explicitOptions); - runner.state.spacetimeServer = 'http://127.0.0.1:3101'; - const secretPath = resolveLocalSpacetimeRuntimeServiceBootstrapSecretPath( - tempDir, - runner.state.spacetimeServer, - options.database, - ); - mkdirSync(join(tempDir, 'dev-runtime-service-bootstrap-secrets'), { - recursive: true, - }); - const untrustedSecret = 'Ab'.repeat(32); - writeFileSync( - secretPath, - `${JSON.stringify({ - schemaVersion: 1, - server: runner.state.spacetimeServer, - database: options.database, - secret: untrustedSecret, - })}\n`, - { mode: 0o644 }, - ); - chmodSync(secretPath, 0o644); + try { + const { explicitOptions, options } = parseArgs( + ['--spacetime-data-dir', tempDir], + {}, + ); + const runner = new DevRunner(options, {}, explicitOptions); + runner.state.spacetimeServer = 'http://127.0.0.1:3101'; + const secretPath = + resolveLocalSpacetimeRuntimeServiceBootstrapSecretPath( + tempDir, + runner.state.spacetimeServer, + options.database, + ); + mkdirSync(join(tempDir, 'dev-runtime-service-bootstrap-secrets'), { + recursive: true, + }); + const untrustedSecret = 'Ab'.repeat(32); + writeFileSync( + secretPath, + `${JSON.stringify({ + schemaVersion: 1, + server: runner.state.spacetimeServer, + database: options.database, + secret: untrustedSecret, + })}\n`, + { mode: 0o644 }, + ); + chmodSync(secretPath, 0o644); - runner.prepareMigrationBootstrapSecret({}); + runner.prepareMigrationBootstrapSecret({}); - expect(runner.runtimeServiceBootstrapSecret).not.toBe(untrustedSecret); - const invalidSecret = `${'a'.repeat(63)}g`; - writeFileSync( - secretPath, - `${JSON.stringify({ - schemaVersion: 1, - server: runner.state.spacetimeServer, - database: options.database, - secret: invalidSecret, - })}\n`, - { mode: 0o600 }, - ); - chmodSync(secretPath, 0o600); - const invalidRunner = new DevRunner(options, {}, explicitOptions); - invalidRunner.state.spacetimeServer = 'http://127.0.0.1:3101'; + expect(runner.runtimeServiceBootstrapSecret).not.toBe(untrustedSecret); + const invalidSecret = `${'a'.repeat(63)}g`; + writeFileSync( + secretPath, + `${JSON.stringify({ + schemaVersion: 1, + server: runner.state.spacetimeServer, + database: options.database, + secret: invalidSecret, + })}\n`, + { mode: 0o600 }, + ); + chmodSync(secretPath, 0o600); + const invalidRunner = new DevRunner(options, {}, explicitOptions); + invalidRunner.state.spacetimeServer = 'http://127.0.0.1:3101'; - invalidRunner.prepareMigrationBootstrapSecret({}); + invalidRunner.prepareMigrationBootstrapSecret({}); - expect(invalidRunner.runtimeServiceBootstrapSecret).not.toBe( - invalidSecret, - ); - expect(invalidRunner.runtimeServiceBootstrapSecret).toMatch( - /^[0-9a-f]{64}$/u, - ); - writeFileSync(secretPath, '', { mode: 0o600 }); - chmodSync(secretPath, 0o600); - const emptyRunner = new DevRunner(options, {}, explicitOptions); - emptyRunner.state.spacetimeServer = 'http://127.0.0.1:3101'; + expect(invalidRunner.runtimeServiceBootstrapSecret).not.toBe( + invalidSecret, + ); + expect(invalidRunner.runtimeServiceBootstrapSecret).toMatch( + /^[0-9a-f]{64}$/u, + ); + writeFileSync(secretPath, '', { mode: 0o600 }); + chmodSync(secretPath, 0o600); + const emptyRunner = new DevRunner(options, {}, explicitOptions); + emptyRunner.state.spacetimeServer = 'http://127.0.0.1:3101'; - emptyRunner.prepareMigrationBootstrapSecret({}); + emptyRunner.prepareMigrationBootstrapSecret({}); - expect(emptyRunner.runtimeServiceBootstrapSecret).toHaveLength(64); - expect(readFileSync(secretPath, 'utf8')).not.toBe(''); - if (process.platform !== 'win32') { - expect(statSync(secretPath).mode & 0o777).toBe(0o600); + expect(emptyRunner.runtimeServiceBootstrapSecret).toHaveLength(64); + expect(readFileSync(secretPath, 'utf8')).not.toBe(''); + if (process.platform !== 'win32') { + expect(statSync(secretPath).mode & 0o777).toBe(0o600); + } + expect(warnSpy).toHaveBeenCalledWith( + expect.stringContaining( + '本地运行服务 bootstrap secret 记录不可用,将重新生成', + ), + ); + } finally { + rmSync(tempDir, { recursive: true, force: true }); } - expect(warnSpy).toHaveBeenCalledWith( - expect.stringContaining( - '本地运行服务 bootstrap secret 记录不可用,将重新生成', - ), - ); - } finally { - rmSync(tempDir, { recursive: true, force: true }); - } - }); + }, + ); test('Vite 子进程环境不包含 SpacetimeDB token 或 bootstrap secret', () => { const env = buildFrontendProcessEnv(