Files
promptfoo--promptfoo/test/providers/golangCompletion.test.ts
T
wehub-resource-sync 0d3cb498a3
CI / Shell Format Check (push) Has been cancelled
CI / Check Ruby (3.4) (push) Has been cancelled
CI / CI Config (push) Has been cancelled
CI / Test on Node ${{ matrix.node }} and ${{ matrix.os }}${{ matrix.shard && format(' (shard {0}/3)', matrix.shard) || '' }} (push) Has been cancelled
CI / Build on Node ${{ matrix.node }} (push) Has been cancelled
CI / Style Check (push) Has been cancelled
CI / Generate Assets (push) Has been cancelled
CI / Check Python (3.14) (push) Has been cancelled
CI / Check Python (3.9) (push) Has been cancelled
CI / Build Docs (push) Has been cancelled
CI / Code Scan Action (push) Has been cancelled
CI / Site tests (push) Has been cancelled
CI / webui tests (push) Has been cancelled
CI / Run Integration Tests (push) Has been cancelled
CI / Run Smoke Tests (push) Has been cancelled
CI / Go Tests (push) Has been cancelled
CI / Share Test (push) Has been cancelled
CI / Redteam (Production API) (push) Has been cancelled
CI / Redteam (Staging API) (push) Has been cancelled
CI / GitHub Actions Lint (push) Has been cancelled
CI / Check Ruby (3.0) (push) Has been cancelled
release-please / release-please (push) Has been cancelled
release-please / build (push) Has been cancelled
release-please / publish-npm (push) Has been cancelled
release-please / publish-npm-backfill (push) Has been cancelled
release-please / docker (push) Has been cancelled
release-please / publish-code-scan-action (push) Has been cancelled
release-please / attest-code-scan-action (push) Has been cancelled
Deploy local.promptfoo.app / Deploy to Cloudflare Pages (push) Has been cancelled
Test and Publish Multi-arch Docker Image / test (push) Has been cancelled
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-amd64 platform:linux/amd64 runner:ubuntu-latest]) (push) Has been cancelled
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-arm64 platform:linux/arm64 runner:ubuntu-24.04-arm]) (push) Has been cancelled
Test and Publish Multi-arch Docker Image / merge-docker-digests (push) Has been cancelled
Test and Publish Multi-arch Docker Image / Attest Multi-arch Image (push) Has been cancelled
Validate Renovate Config / Validate Renovate Configuration (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:24:08 +08:00

976 lines
31 KiB
TypeScript

import fs from 'fs';
import path from 'path';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { GolangProvider } from '../../src/providers/golangCompletion';
// Hoisted mock functions
const mockExecFile = vi.hoisted(() => vi.fn());
const mockGetCache = vi.hoisted(() => vi.fn());
const mockIsCacheEnabled = vi.hoisted(() => vi.fn());
const fsMocks = vi.hoisted(() => ({
readFileSync: vi.fn(),
mkdtempSync: vi.fn(),
existsSync: vi.fn(),
rmSync: vi.fn(),
readdirSync: vi.fn(),
copyFileSync: vi.fn(),
mkdirSync: vi.fn(),
writeFileSync: vi.fn(),
}));
vi.mock('child_process', async (importOriginal) => {
const actual = await importOriginal<typeof import('child_process')>();
return {
...actual,
default: {
...actual,
execFile: mockExecFile,
},
execFile: mockExecFile,
};
});
vi.mock('../../src/cache', () => ({
getCache: mockGetCache,
isCacheEnabled: mockIsCacheEnabled,
}));
vi.mock('../../src/logger', () => ({
default: {
debug: vi.fn(),
error: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
},
}));
vi.mock('fs', async (importOriginal) => {
const actual = await importOriginal<typeof import('fs')>();
return {
...actual,
default: {
...actual,
...fsMocks,
},
...fsMocks,
};
});
vi.mock('fs/promises', () => {
// Wrap each sync mock so the corresponding fs/promises export returns a real
// Promise (or rejects), matching the actual API contract.
const access = vi.fn(async (filePath: any) => {
if (fsMocks.existsSync(filePath)) {
return undefined;
}
throw Object.assign(new Error(`ENOENT: no such file or directory, access '${filePath}'`), {
code: 'ENOENT',
});
});
const readFile = vi.fn(async (...args: any[]) => fsMocks.readFileSync(...args));
const readdir = vi.fn(async (...args: any[]) => fsMocks.readdirSync(...args));
const mkdtemp = vi.fn(async (...args: any[]) => fsMocks.mkdtempSync(...args));
const mkdir = vi.fn(async (...args: any[]) => fsMocks.mkdirSync(...args));
const copyFile = vi.fn(async (...args: any[]) => fsMocks.copyFileSync(...args));
const rm = vi.fn(async (...args: any[]) => fsMocks.rmSync(...args));
const promiseApi = { readFile, readdir, mkdtemp, mkdir, copyFile, rm, access };
return { default: promiseApi, ...promiseApi };
});
vi.mock('path');
vi.mock('../../src/util', async () => {
const actual = await vi.importActual('../../src/util');
return {
...actual,
parsePathOrGlob: vi.fn(() => ({
extension: 'go',
functionName: undefined,
isPathPattern: false,
filePath: '/absolute/path/to/script.go',
})),
};
});
describe('GolangProvider', () => {
const mockReadFileSync = vi.mocked(fs.readFileSync);
const mockResolve = vi.mocked(path.resolve);
const mockMkdtempSync = vi.mocked(fs.mkdtempSync);
const mockExistsSync = vi.mocked(fs.existsSync);
const mockRmSync = vi.mocked(fs.rmSync);
const mockReaddirSync = vi.mocked(fs.readdirSync);
const mockCopyFileSync = vi.mocked(fs.copyFileSync);
const mockMkdirSync = vi.mocked(fs.mkdirSync);
const mockDirname = vi.mocked(path.dirname);
const mockJoin = vi.mocked(path.join);
const mockRelative = vi.mocked(path.relative);
beforeEach(async () => {
vi.clearAllMocks();
mockExecFile.mockReset();
mockGetCache.mockReset();
mockIsCacheEnabled.mockReset();
// Reset all mocks to default behavior
mockGetCache.mockResolvedValue({
get: vi.fn(),
set: vi.fn(),
} as never);
mockIsCacheEnabled.mockReturnValue(false);
// Mock readFileSync to return content for all files
mockReadFileSync.mockImplementation(function (filePath: fs.PathOrFileDescriptor) {
const pathStr = filePath.toString();
if (pathStr.includes('wrapper.go')) {
return 'package main\n// Mock wrapper.go content';
}
if (pathStr.includes('script.go')) {
return 'package main\n// Mock script.go content';
}
return 'mock file content';
});
const mockParsePathOrGlob = vi.mocked((await import('../../src/util')).parsePathOrGlob);
mockParsePathOrGlob.mockImplementation(function (basePath: string, runPath: string) {
if (!basePath && runPath === 'script.go') {
return {
filePath: 'script.go',
functionName: undefined,
isPathPattern: false,
extension: 'go',
};
}
return {
filePath: runPath.replace(/^(file:\/\/|golang:)/, '').split(':')[0],
functionName: runPath.includes(':') ? runPath.split(':')[1] : undefined,
isPathPattern: false,
extension: 'go',
};
});
// Mock path operations
mockResolve.mockImplementation(function (p: string) {
if (!p || typeof p !== 'string') {
return '/absolute/path/undefined';
}
if (p === 'script.go') {
return 'script.go';
}
if (p.includes('script.go')) {
return '/absolute/path/to/script.go';
}
if (p.includes('wrapper.go')) {
return '/absolute/path/to/wrapper.go';
}
return p.startsWith('/') ? p : `/absolute/path/to/${p}`;
});
mockRelative.mockImplementation(function (from: string, to: string) {
if (!from && to === 'script.go') {
return 'script.go';
}
if (to.includes('script.go')) {
return 'script.go';
}
return to;
});
mockDirname.mockImplementation(function (p: string) {
const paths: Record<string, string> = {
'/absolute/path/to/script.go': '/absolute/path/to',
'/absolute/path/to': '/absolute/path',
'/absolute/path': '/absolute',
'/absolute': '/',
};
return paths[p] || p.split('/').slice(0, -1).join('/') || '/';
});
mockJoin.mockImplementation(function (...paths: string[]) {
const validPaths = paths.filter((p) => p !== undefined && p !== null);
if (validPaths.length === 0) {
return '';
}
// Handle specific test cases
if (validPaths.includes('go.mod')) {
const basePath = validPaths.find((p) => p !== 'go.mod') || '/absolute/path/to';
return `${basePath}/go.mod`;
}
if (validPaths.includes('wrapper.go')) {
return '/tmp/golang-provider-xyz/wrapper.go';
}
return validPaths.join('/').replace(/\/+/g, '/');
});
// Mock file system operations
mockMkdtempSync.mockReturnValue('/tmp/golang-provider-xyz');
mockExistsSync.mockImplementation(function (p: fs.PathLike) {
const pathStr = p.toString();
// Always return true for go.mod files and wrapper.go
if (pathStr.includes('go.mod') || pathStr.includes('wrapper.go')) {
return true;
}
return true;
});
// Mock directory reading to return predictable results
mockReaddirSync.mockImplementation(function (p: fs.PathOrFileDescriptor) {
const pathStr = p.toString();
if (pathStr.includes('nested')) {
return [{ name: 'nested-file.go', isDirectory: () => false }] as any;
}
return [{ name: 'test.go', isDirectory: () => false }] as any;
});
// Mock directory and file operations
mockMkdirSync.mockImplementation(function () {
return undefined;
});
mockCopyFileSync.mockImplementation(function () {
return undefined;
});
mockRmSync.mockImplementation(function () {
return undefined;
});
// Mock exec with proper async behavior
mockExecFile.mockImplementation(((
file: string,
args: any,
optionsOrCallback: any,
maybeCallback?: any,
) => {
// Handle both execFile(file, args, callback) and execFile(file, args, options, callback) signatures
const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : maybeCallback;
if (!callback) {
return {} as any;
}
// Use setImmediate to ensure proper async behavior
setImmediate(() => {
try {
if (file === 'go' && args[0] === 'build') {
callback(null, { stdout: '', stderr: '' }, '');
} else if (file.includes('golang_wrapper')) {
callback(null, { stdout: '{"output":"test output"}', stderr: '' }, '');
} else {
callback(new Error('test error'), { stdout: '', stderr: '' }, '');
}
} catch (error) {
callback(error, { stdout: '', stderr: '' }, '');
}
});
return {} as any;
}) as any);
});
describe('constructor', () => {
it('should initialize with correct properties', () => {
const provider = new GolangProvider('script.go', {
id: 'testId',
config: { basePath: '/absolute/path/to' },
});
expect(provider.id()).toBe('testId');
});
it('should initialize with golang: syntax', () => {
const provider = new GolangProvider('golang:script.go', {
id: 'testId',
config: { basePath: '/base' },
});
expect(provider.id()).toBe('testId');
});
it('should initialize with file:// prefix', () => {
const provider = new GolangProvider('file://script.go', {
id: 'testId',
config: { basePath: '/base' },
});
expect(provider.id()).toBe('testId');
});
it('should initialize with file:// prefix and function name', () => {
const provider = new GolangProvider('file://script.go:function_name', {
id: 'testId',
config: { basePath: '/base' },
});
expect(provider.id()).toBe('testId');
});
it('should handle undefined basePath and use default id', () => {
const provider = new GolangProvider('script.go');
expect(provider.id()).toBe('golang:script.go:default');
expect(provider.config).toEqual({});
});
it('should use class id() method when no id is provided in options', () => {
const provider = new GolangProvider('script.go:custom_function');
expect(provider.id()).toBe('golang:script.go:custom_function');
});
it('should allow id() override and later retrieval', () => {
const provider = new GolangProvider('script.go');
expect(provider.id()).toBe('golang:script.go:default');
const originalId = provider.id;
provider.id = () => 'overridden-id';
expect(provider.id()).toBe('overridden-id');
provider.id = originalId;
expect(provider.id()).toBe('golang:script.go:default');
});
});
describe('caching', () => {
it('should use cached result when available', async () => {
const provider = new GolangProvider('script.go', {
config: { basePath: '/absolute/path/to' },
});
mockIsCacheEnabled.mockReturnValue(true);
const mockCache = {
get: vi.fn().mockResolvedValue(JSON.stringify({ output: 'cached result' })),
set: vi.fn(),
};
mockGetCache.mockResolvedValue(mockCache as never);
const result = await provider.callApi('test prompt');
expect(mockCache.get).toHaveBeenCalledWith(expect.stringContaining('golang:'));
expect(mockExecFile).not.toHaveBeenCalled();
expect(result.cached).toBe(true);
expect(result).toEqual({ output: 'cached result', cached: true });
});
it('should handle cache errors', async () => {
const provider = new GolangProvider('script.go', {
config: { basePath: '/absolute/path/to' },
});
mockIsCacheEnabled.mockReturnValue(true);
const mockCache = {
get: vi.fn().mockRejectedValue(new Error('Cache error')),
set: vi.fn(),
};
mockGetCache.mockResolvedValue(mockCache as never);
await expect(provider.callApi('test prompt')).rejects.toThrow('Cache error');
});
it('should handle cache set errors', async () => {
const provider = new GolangProvider('script.go', {
config: { basePath: '/absolute/path/to' },
});
mockIsCacheEnabled.mockReturnValue(true);
const mockCache = {
get: vi.fn().mockResolvedValue(null),
set: vi.fn().mockRejectedValue(new Error('Cache set error')),
};
mockGetCache.mockResolvedValue(mockCache as never);
await expect(provider.callApi('test prompt')).rejects.toThrow('Cache set error');
});
it('should not cache results that contain errors', async () => {
const provider = new GolangProvider('script.go', {
config: { basePath: '/absolute/path/to' },
});
mockIsCacheEnabled.mockReturnValue(true);
const mockCache = {
get: vi.fn().mockResolvedValue(null),
set: vi.fn(),
};
mockGetCache.mockResolvedValue(mockCache as never);
mockExecFile.mockImplementation(((
file: string,
_args: any[],
optionsOrCallback: any,
maybeCallback?: any,
) => {
const callback =
typeof optionsOrCallback === 'function' ? optionsOrCallback : maybeCallback;
if (!callback) {
return {} as any;
}
setImmediate(() => {
if (file.includes('golang_wrapper')) {
callback(null, { stdout: '{"error":"test error in result"}', stderr: '' }, '');
} else {
callback(null, { stdout: '', stderr: '' }, '');
}
});
return {} as any;
}) as any);
const result = await provider.callApi('test prompt');
expect(result).toEqual({ error: 'test error in result' });
expect(mockCache.set).not.toHaveBeenCalled();
});
it('should handle circular references in vars when cache is disabled', async () => {
const provider = new GolangProvider('script.go', {
config: { basePath: '/absolute/path/to' },
});
mockIsCacheEnabled.mockReturnValue(false);
const circularObj: any = { name: 'circular' };
circularObj.self = circularObj;
const spy = vi.spyOn(provider as any, 'executeGolangScript').mockImplementation(function () {
return Promise.resolve({ output: 'mocked result' });
});
const result = await provider.callApi('test prompt', {
prompt: {
raw: 'test prompt',
label: 'test',
},
vars: { circular: circularObj },
});
expect(result).toEqual({ output: 'mocked result' });
expect(spy).toHaveBeenCalledWith(
'test prompt',
expect.objectContaining({
vars: expect.objectContaining({ circular: expect.anything() }),
}),
'call_api',
);
spy.mockRestore();
});
it('should execute script directly when cache is not enabled', async () => {
const provider = new GolangProvider('script.go', {
config: { basePath: '/absolute/path/to' },
});
mockIsCacheEnabled.mockReturnValue(false);
mockGetCache.mockResolvedValue({
get: vi.fn().mockResolvedValue(null),
set: vi.fn(),
} as never);
mockExecFile.mockImplementation(((
_file: string,
_args: any[],
optionsOrCallback: any,
maybeCallback?: any,
) => {
const callback =
typeof optionsOrCallback === 'function' ? optionsOrCallback : maybeCallback;
if (!callback) {
return {} as any;
}
setImmediate(() =>
callback(null, { stdout: '{"output":"direct execution result"}', stderr: '' }, ''),
);
return {} as any;
}) as any);
const result = await provider.callApi('test prompt');
expect(result).toEqual({ output: 'direct execution result' });
const mockCache = await mockGetCache.mock.results[0].value;
expect(mockCache.set).not.toHaveBeenCalled();
});
});
describe('cleanup', () => {
it('should clean up on error', async () => {
const provider = new GolangProvider('script.go', {
config: { basePath: '/absolute/path/to' },
});
mockExecFile.mockImplementation(((
_file: string,
_args: any[],
optionsOrCallback: any,
maybeCallback?: any,
) => {
const callback =
typeof optionsOrCallback === 'function' ? optionsOrCallback : maybeCallback;
if (!callback) {
return {} as any;
}
setImmediate(() => {
callback(new Error('test error'), { stdout: '', stderr: '' }, '');
});
return {} as any;
}) as any);
await expect(provider.callApi('test prompt')).rejects.toThrow(
'Error running Golang script: test error',
);
expect(mockRmSync).toHaveBeenCalledWith(
expect.stringContaining('golang-provider'),
expect.objectContaining({ recursive: true, force: true }),
);
});
});
describe('API methods', () => {
it('should call callEmbeddingApi successfully', async () => {
const provider = new GolangProvider('script.go', {
config: { basePath: '/absolute/path/to' },
});
mockExecFile.mockImplementation(((
file: string,
_args: any[],
optionsOrCallback: any,
maybeCallback?: any,
) => {
const callback =
typeof optionsOrCallback === 'function' ? optionsOrCallback : maybeCallback;
if (!callback) {
return {} as any;
}
setImmediate(() => {
callback(
null,
{
stdout: file.includes('golang_wrapper') ? '{"embedding":[0.1,0.2,0.3]}' : '',
stderr: '',
},
'',
);
});
return {} as any;
}) as any);
const result = await provider.callEmbeddingApi('test prompt');
expect(result).toEqual({ embedding: [0.1, 0.2, 0.3] });
});
it('should call callClassificationApi successfully', async () => {
const provider = new GolangProvider('script.go', {
config: { basePath: '/absolute/path/to' },
});
mockExecFile.mockImplementation(((
file: string,
_args: any[],
optionsOrCallback: any,
maybeCallback?: any,
) => {
const callback =
typeof optionsOrCallback === 'function' ? optionsOrCallback : maybeCallback;
if (!callback) {
return {} as any;
}
setImmediate(() => {
callback(
null,
{
stdout: file.includes('golang_wrapper') ? '{"classification":"test_class"}' : '',
stderr: '',
},
'',
);
});
return {} as any;
}) as any);
const result = await provider.callClassificationApi('test prompt');
expect(result).toEqual({ classification: 'test_class' });
});
it('should handle stderr output without failing', async () => {
const provider = new GolangProvider('script.go', {
config: { basePath: '/absolute/path/to' },
});
mockExecFile.mockImplementation(((
file: string,
_args: any[],
optionsOrCallback: any,
maybeCallback?: any,
) => {
const callback =
typeof optionsOrCallback === 'function' ? optionsOrCallback : maybeCallback;
if (!callback) {
return {} as any;
}
setImmediate(() => {
callback(
null,
{
stdout: file.includes('golang_wrapper') ? '{"output":"test"}' : '',
stderr: 'warning: some go warning',
},
'',
);
});
return {} as any;
}) as any);
const result = await provider.callApi('test prompt');
expect(result).toEqual({ output: 'test' });
});
});
describe('findModuleRoot', () => {
it('should throw error when go.mod is not found', async () => {
mockExistsSync.mockImplementation(function () {
return false;
});
const provider = new GolangProvider('script.go', {
config: { basePath: '/absolute/path/to' },
});
await expect(provider.callApi('test prompt')).rejects.toThrow(
'Could not find go.mod file in any parent directory',
);
});
it('should find go.mod in parent directory', async () => {
const checkedPaths: string[] = [];
mockExistsSync.mockImplementation(function (p: fs.PathLike) {
const pathStr = p.toString();
checkedPaths.push(pathStr);
// Return true for go.mod and wrapper.go files
return pathStr.endsWith('/absolute/path/to/go.mod') || pathStr.includes('wrapper.go');
});
mockDirname.mockImplementation(function (p: string) {
return p.split('/').slice(0, -1).join('/');
});
mockResolve.mockImplementation(function (p: string) {
return p.startsWith('/') ? p : `/absolute/path/to/${p}`;
});
mockJoin.mockImplementation(function (...paths: string[]) {
return paths.join('/').replace(/\/+/g, '/');
});
const provider = new GolangProvider('script.go', {
config: { basePath: '/absolute/path/to/subdir' },
});
mockExecFile.mockImplementation(((
_file: string,
_args: any[],
optionsOrCallback: any,
maybeCallback?: any,
) => {
const callback =
typeof optionsOrCallback === 'function' ? optionsOrCallback : maybeCallback;
if (!callback) {
return {} as any;
}
setImmediate(() => callback(null, { stdout: '{"output":"test"}', stderr: '' }, ''));
return {} as any;
}) as any);
const result = await provider.callApi('test prompt');
expect(result).toEqual({ output: 'test' });
expect(checkedPaths).toContain('/absolute/path/to/go.mod');
expect(mockExistsSync).toHaveBeenCalledWith(
expect.stringContaining('/absolute/path/to/go.mod'),
);
});
});
describe('script execution', () => {
it('should handle JSON parsing errors', async () => {
const provider = new GolangProvider('script.go', {
config: { basePath: '/absolute/path/to' },
});
mockExecFile.mockImplementation(((
file: string,
_args: any[],
optionsOrCallback: any,
maybeCallback?: any,
) => {
const callback =
typeof optionsOrCallback === 'function' ? optionsOrCallback : maybeCallback;
if (!callback) {
return {} as any;
}
setImmediate(() => {
callback(
null,
{
stdout: file.includes('golang_wrapper') ? 'invalid json' : '',
stderr: '',
},
'',
);
});
return {} as any;
}) as any);
await expect(provider.callApi('test prompt')).rejects.toThrow('Error running Golang script');
});
it('should use custom function name when specified', async () => {
const mockParsePathOrGlob = vi.mocked((await import('../../src/util')).parsePathOrGlob);
mockParsePathOrGlob.mockReturnValueOnce({
filePath: '/absolute/path/to/script.go',
functionName: 'custom_function',
isPathPattern: false,
extension: 'go',
});
const provider = new GolangProvider('script.go:custom_function', {
config: { basePath: '/absolute/path/to' },
});
let executedFunctionName = '';
mockExecFile.mockImplementation(((
file: string,
args: any[],
optionsOrCallback: any,
maybeCallback?: any,
) => {
const callback =
typeof optionsOrCallback === 'function' ? optionsOrCallback : maybeCallback;
if (!callback) {
return {} as any;
}
if (file.includes('golang_wrapper')) {
executedFunctionName = args[1]; // args[1] is the function name
}
setImmediate(() => callback(null, { stdout: '{"output":"test"}', stderr: '' }, ''));
return {} as any;
}) as any);
await provider.callApi('test prompt');
expect(executedFunctionName).toBe('custom_function');
});
it('should use custom go executable when specified', async () => {
const provider = new GolangProvider('script.go', {
config: {
basePath: '/absolute/path/to',
goExecutable: '/custom/go',
},
});
let buildFile = '';
mockExecFile.mockImplementation(((
file: string,
args: any[],
optionsOrCallback: any,
maybeCallback?: any,
) => {
const callback =
typeof optionsOrCallback === 'function' ? optionsOrCallback : maybeCallback;
if (!callback) {
return {} as any;
}
if (args?.[0] === 'build') {
buildFile = file;
}
setImmediate(() => callback(null, { stdout: '{"output":"test"}', stderr: '' }, ''));
return {} as any;
}) as any);
await provider.callApi('test prompt');
expect(buildFile).toBe('/custom/go');
});
it('should handle circular references in args', async () => {
const provider = new GolangProvider('script.go', {
config: { basePath: '/absolute/path/to' },
});
// Create circular reference
const circularObj: any = { name: 'circular' };
circularObj.self = circularObj;
// We need to access a private method, so we'll create a spy using any type assertions
const spy = vi.spyOn(provider as any, 'executeGolangScript').mockImplementation(function () {
return Promise.resolve({ output: 'mocked result' });
});
// This should not throw even with circular references
const result = await provider.callApi('test prompt', {
prompt: {
raw: 'test prompt',
label: 'test',
},
vars: { circular: circularObj },
});
// Verify the result and that executeGolangScript was called
expect(result).toEqual({ output: 'mocked result' });
expect(spy).toHaveBeenCalledWith(
'test prompt',
expect.objectContaining({
vars: expect.objectContaining({ circular: expect.anything() }),
}),
'call_api',
);
// Restore the original implementation
spy.mockRestore();
});
});
describe('file operations', () => {
it('should handle directory copy errors', async () => {
const provider = new GolangProvider('script.go', {
config: { basePath: '/absolute/path/to' },
});
mockReaddirSync.mockImplementation(function () {
throw new Error('Directory read error');
});
await expect(provider.callApi('test prompt')).rejects.toThrow('Directory read error');
});
it('should handle file copy errors', async () => {
const provider = new GolangProvider('script.go', {
config: { basePath: '/absolute/path/to' },
});
mockCopyFileSync.mockImplementation(function () {
throw new Error('File copy error');
});
await expect(provider.callApi('test prompt')).rejects.toThrow('File copy error');
});
it('should copy main.go files without transformation', async () => {
const provider = new GolangProvider('script.go', {
config: { basePath: '/absolute/path/to' },
});
const mainGoContent = `
package main
// CallApi declaration
var CallApi = func(prompt string) string {
return "test"
}
func main() {
// Some code
}`;
mockReaddirSync.mockReturnValue([{ name: 'main.go', isDirectory: () => false }] as any);
mockReadFileSync.mockImplementation(function (p: fs.PathOrFileDescriptor) {
if (p.toString().endsWith('main.go')) {
return mainGoContent;
}
return 'other content';
});
const copiedFiles: { src: string; dest: string }[] = [];
mockCopyFileSync.mockImplementation(function (src: fs.PathLike, dest: fs.PathLike) {
copiedFiles.push({ src: src.toString(), dest: dest.toString() });
return undefined;
});
mockExecFile.mockImplementation(((
_file: string,
_args: any[],
optionsOrCallback: any,
maybeCallback?: any,
) => {
const callback =
typeof optionsOrCallback === 'function' ? optionsOrCallback : maybeCallback;
if (!callback) {
return {} as any;
}
setImmediate(() => callback(null, { stdout: '{"output":"test"}', stderr: '' }, ''));
return {} as any;
}) as any);
await provider.callApi('test prompt');
// Verify that main.go was copied, not transformed
expect(copiedFiles).toEqual(
expect.arrayContaining([
expect.objectContaining({
src: expect.stringContaining('main.go'),
dest: expect.stringContaining('main.go'),
}),
]),
);
});
it('should correctly handle nested directories in copyDir', async () => {
const provider = new GolangProvider('script.go', {
config: { basePath: '/absolute/path/to' },
});
// Mock a nested directory structure
mockReaddirSync.mockImplementation(function (p: fs.PathOrFileDescriptor) {
if (p.toString().includes('nested')) {
return [{ name: 'nested-file.go', isDirectory: () => false }] as any;
}
return [
{ name: 'test.go', isDirectory: () => false },
{ name: 'nested', isDirectory: () => true },
] as any;
});
// Track created directories and copied files
const createdDirs: string[] = [];
const copiedFiles: { src: string; dest: string }[] = [];
mockMkdirSync.mockImplementation(function (p: fs.PathLike) {
createdDirs.push(p.toString());
return undefined;
});
mockCopyFileSync.mockImplementation(function (src: fs.PathLike, dest: fs.PathLike) {
copiedFiles.push({ src: src.toString(), dest: dest.toString() });
return undefined;
});
mockExecFile.mockImplementation(((
_file: string,
_args: any[],
optionsOrCallback: any,
maybeCallback?: any,
) => {
const callback =
typeof optionsOrCallback === 'function' ? optionsOrCallback : maybeCallback;
if (!callback) {
return {} as any;
}
setImmediate(() => callback(null, { stdout: '{"output":"test"}', stderr: '' }, ''));
return {} as any;
}) as any);
await provider.callApi('test prompt');
// Check that nested directories were created
expect(createdDirs).toEqual(expect.arrayContaining(['/tmp/golang-provider-xyz']));
// Check that nested files were copied
expect(copiedFiles).toEqual(
expect.arrayContaining([
expect.objectContaining({
dest: expect.stringContaining('nested-file.go'),
}),
]),
);
});
it('should handle copyFileSync errors', async () => {
const provider = new GolangProvider('script.go', {
config: { basePath: '/absolute/path/to' },
});
mockReaddirSync.mockReturnValue([{ name: 'main.go', isDirectory: () => false }] as any);
mockCopyFileSync.mockImplementation(function () {
throw new Error('Failed to copy file');
});
await expect(provider.callApi('test prompt')).rejects.toThrow('Failed to copy file');
});
});
});