-
Notifications
You must be signed in to change notification settings - Fork 925
feat(clawhub): use domestic mirror registry for zh language users #751
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
091025e
9756d76
63a53d3
3f1c2cd
328151c
d6439ab
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,182 @@ | ||
| /** | ||
| * Tests for ClawHub China mirror registry switching logic. | ||
| * | ||
| * Verifies that when the app language starts with 'zh', the spawned ClawHub CLI | ||
| * process receives CLAWHUB_REGISTRY=https://mirror-cn.clawhub.com, and that | ||
| * non-Chinese locales do NOT set this variable. | ||
| */ | ||
| import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; | ||
| import { EventEmitter } from 'events'; | ||
|
|
||
| /* ---------- hoisted mocks ---------- */ | ||
| const { | ||
| mockGetSetting, | ||
| mockExistsSync, | ||
| mockSpawn, | ||
| mockEnsureDir, | ||
| } = vi.hoisted(() => ({ | ||
| mockGetSetting: vi.fn<() => Promise<string | undefined>>(), | ||
| mockExistsSync: vi.fn<(p: string) => boolean>(), | ||
| mockSpawn: vi.fn(), | ||
| mockEnsureDir: vi.fn(), | ||
| })); | ||
|
|
||
| /* ---------- module mocks ---------- */ | ||
| vi.mock('child_process', async () => { | ||
| const actual = await vi.importActual<typeof import('child_process')>('child_process'); | ||
| return { ...actual, default: { ...actual, spawn: mockSpawn }, spawn: mockSpawn }; | ||
| }); | ||
|
|
||
| vi.mock('fs', async () => { | ||
| const actual = await vi.importActual<typeof import('fs')>('fs'); | ||
| return { | ||
| ...actual, | ||
| default: { ...actual, existsSync: mockExistsSync, readdirSync: vi.fn(() => []) }, | ||
| existsSync: mockExistsSync, | ||
| }; | ||
| }); | ||
|
|
||
| vi.mock('electron', () => ({ | ||
| app: { get isPackaged() { return true; } }, | ||
| shell: { openPath: vi.fn() }, | ||
| })); | ||
|
|
||
| vi.mock('@electron/utils/paths', () => ({ | ||
| getOpenClawConfigDir: () => '/tmp/test-openclaw', | ||
| ensureDir: mockEnsureDir, | ||
| getClawHubCliBinPath: () => '/tmp/clawhub-bin', | ||
| getClawHubCliEntryPath: () => '/tmp/clawhub-entry.mjs', | ||
| quoteForCmd: (s: string) => `"${s}"`, | ||
| })); | ||
|
|
||
| vi.mock('@electron/utils/store', () => ({ | ||
| getSetting: (...args: unknown[]) => mockGetSetting(...args), | ||
| })); | ||
|
|
||
| /* ---------- helpers ---------- */ | ||
|
|
||
| /** Create a fake ChildProcess that emits stdout data then exits with code 0. */ | ||
| function makeFakeChild(stdoutData: string = 'ok') { | ||
| const child = new EventEmitter() as ReturnType<typeof import('child_process').spawn>; | ||
| const stdout = new EventEmitter(); | ||
| const stderr = new EventEmitter(); | ||
| (child as any).stdout = stdout; | ||
| (child as any).stderr = stderr; | ||
|
|
||
| // Simulate async output + successful exit | ||
| queueMicrotask(() => { | ||
| stdout.emit('data', Buffer.from(stdoutData)); | ||
| child.emit('close', 0); | ||
| }); | ||
|
|
||
| return child; | ||
| } | ||
|
|
||
| /* ---------- test suite ---------- */ | ||
|
|
||
| describe('ClawHub China mirror registry', () => { | ||
| let spawnEnvCapture: Record<string, string | undefined> | undefined; | ||
|
|
||
| beforeEach(() => { | ||
| vi.resetModules(); | ||
| vi.clearAllMocks(); | ||
|
|
||
| // Default: CLI entry exists so constructor succeeds (useNodeRunner = true path) | ||
| mockExistsSync.mockReturnValue(true); | ||
|
|
||
| // Capture the env passed to spawn | ||
| mockSpawn.mockImplementation((_cmd: string, _args: string[], opts: any) => { | ||
| spawnEnvCapture = opts?.env; | ||
| return makeFakeChild(); | ||
| }); | ||
| }); | ||
|
|
||
| afterEach(() => { | ||
| spawnEnvCapture = undefined; | ||
| }); | ||
|
|
||
| it('sets CLAWHUB_REGISTRY with HTTPS for zh-CN locale', async () => { | ||
| mockGetSetting.mockResolvedValueOnce('zh-CN'); | ||
|
|
||
| const { ClawHubService } = await import('@electron/gateway/clawhub'); | ||
| const service = new ClawHubService(); | ||
|
|
||
| // Use search as the trigger to invoke runCommand | ||
| await service.search({ query: 'test' }); | ||
|
|
||
| expect(spawnEnvCapture).toBeDefined(); | ||
| expect(spawnEnvCapture!.CLAWHUB_REGISTRY).toBe('https://mirror-cn.clawhub.com'); | ||
|
Check failure on line 108 in tests/unit/clawhub-registry.test.ts
|
||
| }); | ||
|
|
||
| it('sets CLAWHUB_REGISTRY with HTTPS for zh-TW locale', async () => { | ||
| mockGetSetting.mockResolvedValueOnce('zh-TW'); | ||
|
|
||
| const { ClawHubService } = await import('@electron/gateway/clawhub'); | ||
| const service = new ClawHubService(); | ||
|
|
||
| await service.search({ query: 'test' }); | ||
|
|
||
| expect(spawnEnvCapture).toBeDefined(); | ||
| expect(spawnEnvCapture!.CLAWHUB_REGISTRY).toBe('https://mirror-cn.clawhub.com'); | ||
|
Check failure on line 120 in tests/unit/clawhub-registry.test.ts
|
||
| }); | ||
|
|
||
| it('sets CLAWHUB_REGISTRY with HTTPS for bare zh locale', async () => { | ||
| mockGetSetting.mockResolvedValueOnce('zh'); | ||
|
|
||
| const { ClawHubService } = await import('@electron/gateway/clawhub'); | ||
| const service = new ClawHubService(); | ||
|
|
||
| await service.search({ query: 'test' }); | ||
|
|
||
| expect(spawnEnvCapture).toBeDefined(); | ||
| expect(spawnEnvCapture!.CLAWHUB_REGISTRY).toBe('https://mirror-cn.clawhub.com'); | ||
|
Check failure on line 132 in tests/unit/clawhub-registry.test.ts
|
||
| }); | ||
|
|
||
| it('does NOT set CLAWHUB_REGISTRY for en locale', async () => { | ||
| mockGetSetting.mockResolvedValueOnce('en'); | ||
|
|
||
| const { ClawHubService } = await import('@electron/gateway/clawhub'); | ||
| const service = new ClawHubService(); | ||
|
|
||
| await service.search({ query: 'test' }); | ||
|
|
||
| expect(spawnEnvCapture).toBeDefined(); | ||
| expect(spawnEnvCapture!.CLAWHUB_REGISTRY).toBeUndefined(); | ||
| }); | ||
|
|
||
| it('does NOT set CLAWHUB_REGISTRY for ja locale', async () => { | ||
| mockGetSetting.mockResolvedValueOnce('ja'); | ||
|
|
||
| const { ClawHubService } = await import('@electron/gateway/clawhub'); | ||
| const service = new ClawHubService(); | ||
|
|
||
| await service.search({ query: 'test' }); | ||
|
|
||
| expect(spawnEnvCapture).toBeDefined(); | ||
| expect(spawnEnvCapture!.CLAWHUB_REGISTRY).toBeUndefined(); | ||
| }); | ||
|
|
||
| it('does NOT set CLAWHUB_REGISTRY when language is undefined', async () => { | ||
| mockGetSetting.mockResolvedValueOnce(undefined); | ||
|
|
||
| const { ClawHubService } = await import('@electron/gateway/clawhub'); | ||
| const service = new ClawHubService(); | ||
|
|
||
| await service.search({ query: 'test' }); | ||
|
|
||
| expect(spawnEnvCapture).toBeDefined(); | ||
| expect(spawnEnvCapture!.CLAWHUB_REGISTRY).toBeUndefined(); | ||
| }); | ||
|
|
||
| it('uses HTTPS, not HTTP, for the registry URL', async () => { | ||
| mockGetSetting.mockResolvedValueOnce('zh-CN'); | ||
|
|
||
| const { ClawHubService } = await import('@electron/gateway/clawhub'); | ||
| const service = new ClawHubService(); | ||
|
|
||
| await service.search({ query: 'test' }); | ||
|
|
||
| expect(spawnEnvCapture!.CLAWHUB_REGISTRY).toMatch(/^https:\/\//); | ||
|
Check failure on line 179 in tests/unit/clawhub-registry.test.ts
|
||
| expect(spawnEnvCapture!.CLAWHUB_REGISTRY).not.toMatch(/^http:\/\//); | ||
| }); | ||
| }); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The feature in this commit is not actually implemented in runtime code:
runCommandstill builds the child process environment with only baseline vars andCLAWHUB_WORKDIR, but never setsCLAWHUB_REGISTRYbased on the UI language. That means zh users continue to use the default registry, and the newly addedtests/unit/clawhub-registry.test.tsexpectations cannot pass becausespawnreceives no registry override forzh*locales.Useful? React with 👍 / 👎.