mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
Adding some simple tests. (#54)
This commit is contained in:
1433
package-lock.json
generated
1433
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -14,6 +14,7 @@
|
|||||||
"lint": "eslint . --ext .ts,.tsx",
|
"lint": "eslint . --ext .ts,.tsx",
|
||||||
"typecheck": "tsc --noEmit --jsx react",
|
"typecheck": "tsc --noEmit --jsx react",
|
||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
|
"preflight": "npm run format --workspaces --if-present && npm run lint --workspaces --if-present && npm run test --workspaces --if-present",
|
||||||
"artifactregistry-login": "npx google-artifactregistry-auth"
|
"artifactregistry-login": "npx google-artifactregistry-auth"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -10,14 +10,15 @@
|
|||||||
"start": "node dist/gemini.js",
|
"start": "node dist/gemini.js",
|
||||||
"debug": "node --inspect-brk dist/gemini.js",
|
"debug": "node --inspect-brk dist/gemini.js",
|
||||||
"lint": "eslint . --ext .ts,.tsx",
|
"lint": "eslint . --ext .ts,.tsx",
|
||||||
"format": "prettier --write ."
|
"format": "prettier --write .",
|
||||||
|
"test": "vitest run"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@google/genai": "^0.8.0",
|
|
||||||
"@gemini-code/server": "1.0.0",
|
"@gemini-code/server": "1.0.0",
|
||||||
|
"@google/genai": "^0.8.0",
|
||||||
"diff": "^7.0.0",
|
"diff": "^7.0.0",
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
"fast-glob": "^3.3.3",
|
"fast-glob": "^3.3.3",
|
||||||
@@ -34,7 +35,8 @@
|
|||||||
"@types/node": "^20.11.24",
|
"@types/node": "^20.11.24",
|
||||||
"@types/react": "^19.1.0",
|
"@types/react": "^19.1.0",
|
||||||
"@types/yargs": "^17.0.32",
|
"@types/yargs": "^17.0.32",
|
||||||
"typescript": "^5.3.3"
|
"typescript": "^5.3.3",
|
||||||
|
"vitest": "^3.1.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
|
|||||||
165
packages/cli/src/core/gemini-client.test.ts
Normal file
165
packages/cli/src/core/gemini-client.test.ts
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
import { describe, it, expect, vi, beforeEach, Mock } from 'vitest';
|
||||||
|
import { GoogleGenAI, Type, Content } from '@google/genai';
|
||||||
|
import { GeminiClient } from './gemini-client.js';
|
||||||
|
import { Config } from '../config/config.js';
|
||||||
|
|
||||||
|
// Mock the entire @google/genai module
|
||||||
|
vi.mock('@google/genai');
|
||||||
|
|
||||||
|
// Mock the Config class and its methods
|
||||||
|
vi.mock('../config/config.js', () => {
|
||||||
|
// The mock constructor should accept the arguments but not explicitly return an object.
|
||||||
|
// vi.fn() will create a mock instance that inherits from the prototype.
|
||||||
|
const MockConfig = vi.fn();
|
||||||
|
// Methods are mocked on the prototype, so instances will inherit them.
|
||||||
|
MockConfig.prototype.getApiKey = vi.fn(() => 'mock-api-key');
|
||||||
|
MockConfig.prototype.getModel = vi.fn(() => 'mock-model');
|
||||||
|
MockConfig.prototype.getTargetDir = vi.fn(() => 'mock-target-dir');
|
||||||
|
return { Config: MockConfig };
|
||||||
|
});
|
||||||
|
|
||||||
|
// Define a type for the mocked GoogleGenAI instance structure
|
||||||
|
type MockGoogleGenAIType = {
|
||||||
|
models: {
|
||||||
|
generateContent: Mock;
|
||||||
|
};
|
||||||
|
chats: {
|
||||||
|
create: Mock;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('GeminiClient', () => {
|
||||||
|
// Use the specific types defined above
|
||||||
|
let mockGenerateContent: MockGoogleGenAIType['models']['generateContent'];
|
||||||
|
let mockGoogleGenAIInstance: MockGoogleGenAIType;
|
||||||
|
let config: Config;
|
||||||
|
let client: GeminiClient;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
|
||||||
|
// Mock the generateContent method specifically
|
||||||
|
mockGenerateContent = vi.fn();
|
||||||
|
|
||||||
|
// Mock the chainable structure ai.models.generateContent
|
||||||
|
mockGoogleGenAIInstance = {
|
||||||
|
models: {
|
||||||
|
generateContent: mockGenerateContent,
|
||||||
|
},
|
||||||
|
chats: {
|
||||||
|
create: vi.fn(), // Mock create as well
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Configure the mocked GoogleGenAI constructor to return our mock instance
|
||||||
|
(GoogleGenAI as Mock).mockImplementation(() => mockGoogleGenAIInstance);
|
||||||
|
|
||||||
|
config = new Config('mock-api-key-arg', 'mock-model-arg', 'mock-dir-arg');
|
||||||
|
client = new GeminiClient(config);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('generateJson', () => {
|
||||||
|
it('should call ai.models.generateContent with correct parameters', async () => {
|
||||||
|
const mockContents: Content[] = [
|
||||||
|
{ role: 'user', parts: [{ text: 'test prompt' }] },
|
||||||
|
];
|
||||||
|
const mockSchema = {
|
||||||
|
type: Type.OBJECT,
|
||||||
|
properties: { key: { type: Type.STRING } },
|
||||||
|
};
|
||||||
|
const mockApiResponse = { text: JSON.stringify({ key: 'value' }) };
|
||||||
|
|
||||||
|
mockGenerateContent.mockResolvedValue(mockApiResponse);
|
||||||
|
await client.generateJson(mockContents, mockSchema);
|
||||||
|
|
||||||
|
expect(mockGenerateContent).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
// Use expect.objectContaining for the config assertion
|
||||||
|
const expectedConfigMatcher = expect.objectContaining({
|
||||||
|
temperature: 0,
|
||||||
|
topP: 1,
|
||||||
|
systemInstruction: expect.any(String),
|
||||||
|
responseSchema: mockSchema,
|
||||||
|
responseMimeType: 'application/json',
|
||||||
|
});
|
||||||
|
expect(mockGenerateContent).toHaveBeenCalledWith({
|
||||||
|
model: 'mock-model',
|
||||||
|
config: expectedConfigMatcher,
|
||||||
|
contents: mockContents,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the parsed JSON response', async () => {
|
||||||
|
const mockContents: Content[] = [
|
||||||
|
{ role: 'user', parts: [{ text: 'test prompt' }] },
|
||||||
|
];
|
||||||
|
const mockSchema = {
|
||||||
|
type: Type.OBJECT,
|
||||||
|
properties: { key: { type: Type.STRING } },
|
||||||
|
};
|
||||||
|
const expectedJson = { key: 'value' };
|
||||||
|
const mockApiResponse = { text: JSON.stringify(expectedJson) };
|
||||||
|
|
||||||
|
mockGenerateContent.mockResolvedValue(mockApiResponse);
|
||||||
|
|
||||||
|
const result = await client.generateJson(mockContents, mockSchema);
|
||||||
|
|
||||||
|
expect(result).toEqual(expectedJson);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error if API returns empty response', async () => {
|
||||||
|
const mockContents: Content[] = [
|
||||||
|
{ role: 'user', parts: [{ text: 'test prompt' }] },
|
||||||
|
];
|
||||||
|
const mockSchema = {
|
||||||
|
type: Type.OBJECT,
|
||||||
|
properties: { key: { type: Type.STRING } },
|
||||||
|
};
|
||||||
|
const mockApiResponse = { text: '' }; // Empty response
|
||||||
|
|
||||||
|
mockGenerateContent.mockResolvedValue(mockApiResponse);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
client.generateJson(mockContents, mockSchema),
|
||||||
|
).rejects.toThrow(
|
||||||
|
'Failed to generate JSON content: API returned an empty response.',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error if API response is not valid JSON', async () => {
|
||||||
|
const mockContents: Content[] = [
|
||||||
|
{ role: 'user', parts: [{ text: 'test prompt' }] },
|
||||||
|
];
|
||||||
|
const mockSchema = {
|
||||||
|
type: Type.OBJECT,
|
||||||
|
properties: { key: { type: Type.STRING } },
|
||||||
|
};
|
||||||
|
const mockApiResponse = { text: 'invalid json' }; // Invalid JSON
|
||||||
|
|
||||||
|
mockGenerateContent.mockResolvedValue(mockApiResponse);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
client.generateJson(mockContents, mockSchema),
|
||||||
|
).rejects.toThrow('Failed to parse API response as JSON:');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error if generateContent rejects', async () => {
|
||||||
|
const mockContents: Content[] = [
|
||||||
|
{ role: 'user', parts: [{ text: 'test prompt' }] },
|
||||||
|
];
|
||||||
|
const mockSchema = {
|
||||||
|
type: Type.OBJECT,
|
||||||
|
properties: { key: { type: Type.STRING } },
|
||||||
|
};
|
||||||
|
const apiError = new Error('API call failed');
|
||||||
|
|
||||||
|
mockGenerateContent.mockRejectedValue(apiError);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
client.generateJson(mockContents, mockSchema),
|
||||||
|
).rejects.toThrow(`Failed to generate JSON content: ${apiError.message}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Add tests for startChat and sendMessageStream later
|
||||||
|
});
|
||||||
9
packages/cli/src/index.test.ts
Normal file
9
packages/cli/src/index.test.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { toolRegistry } from './tools/tool-registry.js';
|
||||||
|
|
||||||
|
describe('cli tests', () => {
|
||||||
|
it('should have a tool registry', () => {
|
||||||
|
expect(toolRegistry).toBeDefined();
|
||||||
|
expect(typeof toolRegistry.registerTool).toBe('function');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -9,7 +9,8 @@
|
|||||||
"target": "ES2020",
|
"target": "ES2020",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@gemini-code/*": ["./packages/*"]
|
"@gemini-code/*": ["./packages/*"]
|
||||||
}
|
},
|
||||||
|
"types": ["node", "vitest/globals"]
|
||||||
},
|
},
|
||||||
"exclude": ["node_modules", "dist"],
|
"exclude": ["node_modules", "dist"],
|
||||||
"include": ["src"],
|
"include": ["src"],
|
||||||
|
|||||||
@@ -8,14 +8,15 @@
|
|||||||
"build": "tsc --build && cp package.json dist/",
|
"build": "tsc --build && cp package.json dist/",
|
||||||
"clean": "rm -rf dist",
|
"clean": "rm -rf dist",
|
||||||
"lint": "eslint . --ext .ts,.tsx",
|
"lint": "eslint . --ext .ts,.tsx",
|
||||||
"format": "prettier --write ."
|
"format": "prettier --write .",
|
||||||
|
"test": "vitest run"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
"dependencies": {},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5.3.3"
|
"typescript": "^5.3.3",
|
||||||
|
"vitest": "^3.1.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
|
|||||||
9
packages/server/src/index.test.ts
Normal file
9
packages/server/src/index.test.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { helloServer } from './index.js';
|
||||||
|
|
||||||
|
describe('server tests', () => {
|
||||||
|
it('should export helloServer function', () => {
|
||||||
|
expect(helloServer).toBeDefined();
|
||||||
|
expect(typeof helloServer).toBe('function');
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user