Skip to content
Open
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
46 changes: 40 additions & 6 deletions tests/integration/skills/script_js/agent_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/
import {exec, spawn} from 'node:child_process';
import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import {promisify} from 'node:util';
import {afterAll, beforeAll, describe, expect, it} from 'vitest';
import {normalizeLineEndings, sendInput} from '../../test_case_utils.js';
Expand All @@ -14,6 +15,35 @@ const dirname = process.cwd();
const PROJECT_PATH = `${dirname}/tests/integration/skills/script_js`;
const TEST_EXECUTION_TIMEOUT = 60000;

/**
* Files the skill script materializes into the fixture directory. The write
* path de-duplicates against existing files by appending `_2`, `_3`, ...
* before the extension (materializeFiles in core/src/utils/file_utils.ts), so
* a teardown keyed on these exact names leaves the variants behind.
*/
const GENERATED_FILE_NAMES = [
'ephemeral_entanglement.md',
'index.html',
'sketch.js',
];

/** True for a generated output name or one of its `_<n>` de-duplicated variants. */
function isGeneratedOutput(entry: string): boolean {
const ext = path.extname(entry);
const base = path.basename(entry, ext);
return GENERATED_FILE_NAMES.includes(`${base.replace(/_\d+$/, '')}${ext}`);
}

/** Removes every generated output in the fixture directory, variants included. */
async function removeGeneratedOutputs(): Promise<void> {
const entries = await fs.readdir(PROJECT_PATH);
await Promise.all(
entries
.filter(isGeneratedOutput)
.map((entry) => fs.rm(path.join(PROJECT_PATH, entry), {force: true})),
);
}

/**
* This integration test verifies that an agent equipped with script execution skills
* can successfully process a request to create algorithmic art.
Expand All @@ -30,6 +60,8 @@ const TEST_EXECUTION_TIMEOUT = 60000;
*/
describe('Agent with skills that generates JS script and runs it locally', () => {
beforeAll(async () => {
// Start from a clean fixture dir; see GENERATED_FILE_NAMES.
await removeGeneratedOutputs();
await execAsync('npm install', {cwd: PROJECT_PATH});
}, TEST_EXECUTION_TIMEOUT);

Expand Down Expand Up @@ -88,17 +120,19 @@ describe('Agent with skills that generates JS script and runs it locally', () =>
expect((normalizeLineEndings(resultHtmlFile) as string).trim()).toEqual(
(normalizeLineEndings(expectedHtmlFile) as string).trim(),
);

// Fail loudly if the run produced a `_<n>` variant.
const generated = (await fs.readdir(PROJECT_PATH)).filter(
isGeneratedOutput,
);
expect(generated.sort()).toEqual([...GENERATED_FILE_NAMES].sort());
},
TEST_EXECUTION_TIMEOUT,
);

afterAll(async () => {
// delete generated files
await fs
.rm(`${PROJECT_PATH}/ephemeral_entanglement.md`, {force: true})
.catch(() => {});
await fs.rm(`${PROJECT_PATH}/index.html`, {force: true}).catch(() => {});
await fs.rm(`${PROJECT_PATH}/sketch.js`, {force: true}).catch(() => {});
// By name shape, not exact name: variants must go too.
await removeGeneratedOutputs().catch(() => {});

await fs
.rm(`${PROJECT_PATH}/node_modules`, {recursive: true, force: true})
Expand Down
Loading