Download packages/cli/src/config/extensionRegistryClient.test.ts from SaylorTwift/gemini-cli: direct link, hf CLI and curl.
- Browser
- Download file 8.83 kB
-
https://huggingface.co/SaylorTwift/gemini-cli/resolve/main/packages/cli/src/config/extensionRegistryClient.test.ts
- Command line
-
hf download hf://SaylorTwift/gemini-cli/packages/cli/src/config/extensionRegistryClient.test.ts
-
curl -L -o extensionRegistryClient.test.ts https://huggingface.co/SaylorTwift/gemini-cli/resolve/main/packages/cli/src/config/extensionRegistryClient.test.ts
8.83 kB
| /** | |
| * @license | |
| * Copyright 2026 Google LLC | |
| * SPDX-License-Identifier: Apache-2.0 | |
| */ | |
| import { | |
| describe, | |
| it, | |
| expect, | |
| vi, | |
| beforeEach, | |
| afterEach, | |
| type Mock, | |
| } from 'vitest'; | |
| import * as fs from 'node:fs/promises'; | |
| import { | |
| ExtensionRegistryClient, | |
| type RegistryExtension, | |
| } from './extensionRegistryClient.js'; | |
| import { fetchWithTimeout, resolveToRealPath } from '@google/gemini-cli-core'; | |
| vi.mock('@google/gemini-cli-core', async (importOriginal) => { | |
| const actual = | |
| await importOriginal<typeof import('@google/gemini-cli-core')>(); | |
| return { | |
| ...actual, | |
| fetchWithTimeout: vi.fn(), | |
| }; | |
| }); | |
| vi.mock('node:fs/promises', () => ({ | |
| readFile: vi.fn(), | |
| })); | |
| const mockExtensions: RegistryExtension[] = [ | |
| { | |
| id: 'ext1', | |
| rank: 1, | |
| url: 'https://github.com/test/ext1', | |
| fullName: 'test/ext1', | |
| repoDescription: 'Test extension 1', | |
| stars: 100, | |
| lastUpdated: '2025-01-01T00:00:00Z', | |
| extensionName: 'extension-one', | |
| extensionVersion: '1.0.0', | |
| extensionDescription: 'First test extension', | |
| avatarUrl: 'https://example.com/avatar1.png', | |
| hasMCP: true, | |
| hasContext: false, | |
| isGoogleOwned: false, | |
| licenseKey: 'mit', | |
| hasHooks: false, | |
| hasCustomCommands: false, | |
| hasSkills: false, | |
| }, | |
| { | |
| id: 'ext2', | |
| rank: 2, | |
| url: 'https://github.com/test/ext2', | |
| fullName: 'test/ext2', | |
| repoDescription: 'Test extension 2', | |
| stars: 50, | |
| lastUpdated: '2025-01-02T00:00:00Z', | |
| extensionName: 'extension-two', | |
| extensionVersion: '0.5.0', | |
| extensionDescription: 'Second test extension', | |
| avatarUrl: 'https://example.com/avatar2.png', | |
| hasMCP: false, | |
| hasContext: true, | |
| isGoogleOwned: true, | |
| licenseKey: 'apache-2.0', | |
| hasHooks: false, | |
| hasCustomCommands: false, | |
| hasSkills: false, | |
| }, | |
| { | |
| id: 'ext3', | |
| rank: 3, | |
| url: 'https://github.com/test/ext3', | |
| fullName: 'test/ext3', | |
| repoDescription: 'Test extension 3', | |
| stars: 10, | |
| lastUpdated: '2025-01-03T00:00:00Z', | |
| extensionName: 'extension-three', | |
| extensionVersion: '0.1.0', | |
| extensionDescription: 'Third test extension', | |
| avatarUrl: 'https://example.com/avatar3.png', | |
| hasMCP: true, | |
| hasContext: true, | |
| isGoogleOwned: false, | |
| licenseKey: 'gpl-3.0', | |
| hasHooks: false, | |
| hasCustomCommands: false, | |
| hasSkills: false, | |
| }, | |
| ]; | |
| describe('ExtensionRegistryClient', () => { | |
| let client: ExtensionRegistryClient; | |
| let fetchMock: Mock; | |
| beforeEach(() => { | |
| ExtensionRegistryClient.resetCache(); | |
| client = new ExtensionRegistryClient(); | |
| fetchMock = fetchWithTimeout as Mock; | |
| fetchMock.mockReset(); | |
| }); | |
| afterEach(() => { | |
| vi.restoreAllMocks(); | |
| }); | |
| it('should fetch and return extensions with pagination (default ranking)', async () => { | |
| fetchMock.mockResolvedValue({ | |
| ok: true, | |
| json: async () => mockExtensions, | |
| }); | |
| const result = await client.getExtensions(1, 2); | |
| expect(result.extensions).toHaveLength(2); | |
| expect(result.extensions[0].id).toBe('ext1'); // rank 1 | |
| expect(result.extensions[1].id).toBe('ext2'); // rank 2 | |
| expect(result.total).toBe(3); | |
| expect(fetchMock).toHaveBeenCalledTimes(1); | |
| expect(fetchMock).toHaveBeenCalledWith( | |
| 'https://geminicli.com/extensions.json', | |
| 10000, | |
| ); | |
| }); | |
| it('should return extensions sorted alphabetically', async () => { | |
| fetchMock.mockResolvedValue({ | |
| ok: true, | |
| json: async () => mockExtensions, | |
| }); | |
| const result = await client.getExtensions(1, 3, 'alphabetical'); | |
| expect(result.extensions).toHaveLength(3); | |
| expect(result.extensions[0].id).toBe('ext1'); | |
| expect(result.extensions[1].id).toBe('ext3'); | |
| expect(result.extensions[2].id).toBe('ext2'); | |
| }); | |
| it('should return the second page of extensions', async () => { | |
| fetchMock.mockResolvedValue({ | |
| ok: true, | |
| json: async () => mockExtensions, | |
| }); | |
| const result = await client.getExtensions(2, 2); | |
| expect(result.extensions).toHaveLength(1); | |
| expect(result.extensions[0].id).toBe('ext3'); | |
| expect(result.total).toBe(3); | |
| }); | |
| it('should search extensions by name', async () => { | |
| fetchMock.mockResolvedValue({ | |
| ok: true, | |
| json: async () => mockExtensions, | |
| }); | |
| const results = await client.searchExtensions('one'); | |
| expect(results.length).toBeGreaterThanOrEqual(1); | |
| expect(results[0].id).toBe('ext1'); | |
| }); | |
| it('should search extensions by description', async () => { | |
| fetchMock.mockResolvedValue({ | |
| ok: true, | |
| json: async () => mockExtensions, | |
| }); | |
| const results = await client.searchExtensions('Second'); | |
| expect(results.length).toBeGreaterThanOrEqual(1); | |
| expect(results[0].id).toBe('ext2'); | |
| }); | |
| it('should get an extension by ID', async () => { | |
| fetchMock.mockResolvedValue({ | |
| ok: true, | |
| json: async () => mockExtensions, | |
| }); | |
| const result = await client.getExtension('ext2'); | |
| expect(result).toBeDefined(); | |
| expect(result?.id).toBe('ext2'); | |
| }); | |
| it('should return undefined if extension not found', async () => { | |
| fetchMock.mockResolvedValue({ | |
| ok: true, | |
| json: async () => mockExtensions, | |
| }); | |
| const result = await client.getExtension('non-existent'); | |
| expect(result).toBeUndefined(); | |
| }); | |
| it('should cache the fetch result', async () => { | |
| fetchMock.mockResolvedValue({ | |
| ok: true, | |
| json: async () => mockExtensions, | |
| }); | |
| await client.getExtensions(); | |
| await client.getExtensions(); | |
| expect(fetchMock).toHaveBeenCalledTimes(1); | |
| }); | |
| it('should share the fetch result across instances', async () => { | |
| fetchMock.mockResolvedValue({ | |
| ok: true, | |
| json: async () => mockExtensions, | |
| }); | |
| const client1 = new ExtensionRegistryClient(); | |
| const client2 = new ExtensionRegistryClient(); | |
| await client1.getExtensions(); | |
| await client2.getExtensions(); | |
| expect(fetchMock).toHaveBeenCalledTimes(1); | |
| }); | |
| it('should throw an error if fetch fails', async () => { | |
| fetchMock.mockResolvedValue({ | |
| ok: false, | |
| statusText: 'Not Found', | |
| }); | |
| await expect(client.getExtensions()).rejects.toThrow( | |
| 'Failed to fetch extensions: Not Found', | |
| ); | |
| }); | |
| it('should not return irrelevant results', async () => { | |
| fetchMock.mockResolvedValue({ | |
| ok: true, | |
| json: async () => [ | |
| ...mockExtensions, | |
| { | |
| id: 'dataplex', | |
| extensionName: 'dataplex', | |
| extensionDescription: 'Connect to Dataplex Universal Catalog...', | |
| fullName: 'google-cloud/dataplex', | |
| rank: 6, | |
| stars: 6, | |
| url: '', | |
| repoDescription: '', | |
| lastUpdated: '', | |
| extensionVersion: '1.0.0', | |
| avatarUrl: '', | |
| hasMCP: false, | |
| hasContext: false, | |
| isGoogleOwned: true, | |
| licenseKey: '', | |
| hasHooks: false, | |
| hasCustomCommands: false, | |
| hasSkills: false, | |
| }, | |
| { | |
| id: 'conductor', | |
| extensionName: 'conductor', | |
| extensionDescription: 'A conductor extension that actually matches.', | |
| fullName: 'someone/conductor', | |
| rank: 100, | |
| stars: 100, | |
| url: '', | |
| repoDescription: '', | |
| lastUpdated: '', | |
| extensionVersion: '1.0.0', | |
| avatarUrl: '', | |
| hasMCP: false, | |
| hasContext: false, | |
| isGoogleOwned: false, | |
| licenseKey: '', | |
| hasHooks: false, | |
| hasCustomCommands: false, | |
| hasSkills: false, | |
| }, | |
| ], | |
| }); | |
| const results = await client.searchExtensions('conductor'); | |
| const ids = results.map((r) => r.id); | |
| expect(ids).not.toContain('dataplex'); | |
| expect(ids).toContain('conductor'); | |
| }); | |
| it('should fetch extensions from a local file path', async () => { | |
| const filePath = '/path/to/extensions.json'; | |
| const clientWithFile = new ExtensionRegistryClient(filePath); | |
| const mockReadFile = vi.mocked(fs.readFile); | |
| mockReadFile.mockResolvedValue(JSON.stringify(mockExtensions)); | |
| const result = await clientWithFile.getExtensions(); | |
| expect(result.extensions).toHaveLength(3); | |
| expect(mockReadFile).toHaveBeenCalledWith( | |
| resolveToRealPath(filePath), | |
| 'utf-8', | |
| ); | |
| }); | |
| it('should fetch extensions from a file:// URL', async () => { | |
| const fileUrl = 'file:///path/to/extensions.json'; | |
| const clientWithFileUrl = new ExtensionRegistryClient(fileUrl); | |
| const mockReadFile = vi.mocked(fs.readFile); | |
| mockReadFile.mockResolvedValue(JSON.stringify(mockExtensions)); | |
| const result = await clientWithFileUrl.getExtensions(); | |
| expect(result.extensions).toHaveLength(3); | |
| expect(mockReadFile).toHaveBeenCalledWith( | |
| resolveToRealPath(fileUrl), | |
| 'utf-8', | |
| ); | |
| }); | |
| }); | |