Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions packages/ai/__tests__/fetchAiMockResponses.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { execFileSync } from 'child_process';
import { existsSync, readdirSync } from 'fs';
import { rimrafSync } from 'rimraf';
import { afterEach, describe, expect, it, jest } from '@jest/globals';

import { fetchAiMockResponses } from '../../../scripts/fetch_ai_mock_responses';

jest.mock('child_process', () => ({
execFileSync: jest.fn(),
}));
jest.mock('fs', () => ({
existsSync: jest.fn(),
readdirSync: jest.fn(),
}));
jest.mock('rimraf', () => ({
rimrafSync: jest.fn(),
}));

describe('fetchAiMockResponses', () => {
afterEach(() => {
jest.clearAllMocks();
});

it('passes a repository-controlled tag to git without shell interpretation', () => {
const maliciousTag = 'v17.0;id';
jest.mocked(existsSync).mockReturnValue(false);
jest
.mocked(execFileSync)
.mockReturnValueOnce(`hash refs/tags/${maliciousTag}`)
.mockReturnValueOnce('');

fetchAiMockResponses();

expect(execFileSync).toHaveBeenNthCalledWith(
1,
'git',
['ls-remote', '--tags', '--sort=version:refname', expect.any(String)],
{ encoding: 'utf8' },
);
expect(execFileSync).toHaveBeenNthCalledWith(2, 'git', [
'-c',
'advice.detachedHead=false',
'clone',
'--branch',
maliciousTag,
'--',
expect.any(String),
expect.any(String),
]);
expect(rimrafSync).toHaveBeenCalledTimes(1);
});

it('exits early without touching git when a matching clone already exists locally', () => {
const exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => {
throw new Error('process.exit called');
});
jest.mocked(existsSync).mockReturnValue(true);
jest
.mocked(readdirSync)
.mockReturnValue([
{ name: 'vertexai-sdk-test-data_v17.0', isDirectory: () => true },
] as unknown as ReturnType<typeof readdirSync>);

expect(() => fetchAiMockResponses()).toThrow('process.exit called');

expect(exitSpy).toHaveBeenCalledWith(0);
expect(execFileSync).not.toHaveBeenCalled();
exitSpy.mockRestore();
});

it('exits early without cloning when the target directory for the latest tag already exists', () => {
const exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => {
throw new Error('process.exit called');
});
jest
.mocked(existsSync)
.mockReturnValueOnce(false) // no existing clone dir found yet
.mockReturnValueOnce(true); // target dir for the latest tag already exists
jest.mocked(execFileSync).mockReturnValueOnce('hash refs/tags/v18.0');

expect(() => fetchAiMockResponses()).toThrow('process.exit called');

expect(exitSpy).toHaveBeenCalledWith(0);
expect(execFileSync).toHaveBeenCalledTimes(1); // ls-remote only, never clone
exitSpy.mockRestore();
});

it('cleans stale fixture data while preserving TypeScript sources and the latest tag directory', () => {
jest.mocked(existsSync).mockReturnValue(false);
jest.mocked(execFileSync).mockReturnValueOnce('hash refs/tags/v19.0').mockReturnValueOnce('');

fetchAiMockResponses();

const [, rimrafOptions] = jest.mocked(rimrafSync).mock.calls[0];
const { filter } = rimrafOptions as unknown as {
filter: (path: string, stat: unknown) => boolean;
};

// filter returns true for entries rimraf should delete, false to keep them.
expect(filter('/root/keep.ts', {})).toBe(false);
expect(filter('/root/vertexai-sdk-test-data_v19.0/fixture.json', {})).toBe(false);
expect(filter('/root/vertexai-sdk-test-data_v18.0/stale.json', {})).toBe(true);
});
});
81 changes: 48 additions & 33 deletions scripts/fetch_ai_mock_responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
// clone of the shared repository of Vertex AI test data.

// eslint-disable-next-line @typescript-eslint/no-require-imports
import { execSync } from 'child_process';
import { execFileSync } from 'child_process';
import { join } from 'path';
import { existsSync, readdirSync } from 'fs';
import { rimrafSync } from 'rimraf';
Expand All @@ -37,42 +37,57 @@ function findExistingCloneDirName(): string | undefined {
.at(0);
}

const existingCloneDirName = findExistingCloneDirName();
if (existingCloneDirName !== undefined) {
console.log('AI mock responses data exists locally already. Exiting fetch script.');
process.exit(0);
}
export function fetchAiMockResponses(): void {
const existingCloneDirName = findExistingCloneDirName();
if (existingCloneDirName !== undefined) {
console.log('AI mock responses data exists locally already. Exiting fetch script.');
process.exit(0);
}

// Get tags from repository, sorted by tag name, and coerce result to a string, then trim it
const repoTags = (
execSync(`git ls-remote --tags --sort=version:refname "${REPO_LINK}"`) + ''
).trim();
// Get tags from repository, sorted by tag name, and coerce result to a string, then trim it
const repoTags = execFileSync(
'git',
['ls-remote', '--tags', '--sort=version:refname', REPO_LINK],
{ encoding: 'utf8' },
).trim();

// Fish out just the tag name from the last line (since they are sorted already)
const latestTag = repoTags.split('/').at(-1);
if (latestTag === undefined) {
console.error('Unable to determine latest test data tag.');
process.exit(1);
}
// Fish out just the tag name from the last line (since they are sorted already)
const latestTag = repoTags.split('/').at(-1);
if (latestTag === undefined) {
console.error('Unable to determine latest test data tag.');
process.exit(1);
}

// Create the test data directory based on the latest tag
const cloneDirName = `${REPO_NAME}_${latestTag}`;
const cloneDirPath = join(TEST_DATA_ROOT, cloneDirName);
// Create the test data directory based on the latest tag
const cloneDirName = `${REPO_NAME}_${latestTag}`;
const cloneDirPath = join(TEST_DATA_ROOT, cloneDirName);

// Clean out any test data that isn't the latest test data
rimrafSync(TEST_DATA_ROOT, {
preserveRoot: false,
filter: (path: string, _) => !path.endsWith('.ts') && !path.includes(cloneDirName),
});
// Clean out any test data that isn't the latest test data
rimrafSync(TEST_DATA_ROOT, {
preserveRoot: false,
filter: (path: string, _) => !path.endsWith('.ts') && !path.includes(cloneDirName),
});

// Exit if our intended latest data clone target already exists
if (existsSync(cloneDirPath)) {
console.log('AI mock responses data exists locally already. Exiting fetch script.');
process.exit(0);
// Exit if our intended latest data clone target already exists
if (existsSync(cloneDirPath)) {
console.log('AI mock responses data exists locally already. Exiting fetch script.');
process.exit(0);
}

// Clone the latest test data
console.log(`Fetching AI mock responses data...`);
execFileSync('git', [
'-c',
'advice.detachedHead=false',
'clone',
'--branch',
latestTag,
'--',
REPO_LINK,
cloneDirPath,
]);
}

// Clone the latest test data
console.log(`Fetching AI mock responses data...`);
execSync(
`git -c advice.detachedHead=false clone --branch ${latestTag} ${REPO_LINK} ${cloneDirPath}`,
);
if (require.main === module) {
fetchAiMockResponses();
}
Loading