-
Notifications
You must be signed in to change notification settings - Fork 327
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #20 from tpaulshippy/mock-llm
Integration test with mock LLM
- Loading branch information
Showing
11 changed files
with
266 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import { readFile, writeFile } from 'fs/promises'; | ||
import { KnownError } from './error'; | ||
import { formatMessage } from './test'; | ||
import OpenAI from 'openai'; | ||
|
||
const readMockLlmRecordFile = async ( | ||
mockLlmRecordFile: string | ||
): Promise<{ completions: any[] }> => { | ||
const mockLlmRecordFileContents = await readFile( | ||
mockLlmRecordFile, | ||
'utf-8' | ||
).catch(() => ''); | ||
let jsonLlmRecording; | ||
try { | ||
jsonLlmRecording = JSON.parse(mockLlmRecordFileContents.toString()); | ||
} catch { | ||
jsonLlmRecording = { completions: [] }; | ||
} | ||
return jsonLlmRecording; | ||
}; | ||
|
||
export const captureLlmRecord = async ( | ||
messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[], | ||
output: string, | ||
mockLlmRecordFile?: string | ||
) => { | ||
if (mockLlmRecordFile) { | ||
const jsonLlmRecording = await readMockLlmRecordFile(mockLlmRecordFile); | ||
|
||
jsonLlmRecording.completions.push({ | ||
inputs: messages, | ||
output: output, | ||
}); | ||
|
||
await writeFile( | ||
mockLlmRecordFile, | ||
JSON.stringify(jsonLlmRecording, null, 2) | ||
); | ||
} | ||
}; | ||
export const mockedLlmCompletion = async ( | ||
mockLlmRecordFile: string | undefined, | ||
messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] | ||
) => { | ||
if (!mockLlmRecordFile) { | ||
throw new KnownError( | ||
'You need to set the MOCK_LLM_RECORD_FILE environment variable to use the mock LLM' | ||
); | ||
} | ||
const jsonLlmRecording = await readMockLlmRecordFile(mockLlmRecordFile); | ||
const completion = jsonLlmRecording.completions.find( | ||
(completion: { inputs: any }) => { | ||
// Match on system input only | ||
return ( | ||
JSON.stringify(completion.inputs[0]) === JSON.stringify(messages[0]) | ||
); | ||
} | ||
); | ||
if (!completion) { | ||
throw new KnownError( | ||
`No completion found for the given system input in the MOCK_LLM_RECORD_FILE: ${JSON.stringify( | ||
messages[0] | ||
)}` | ||
); | ||
} | ||
process.stdout.write(formatMessage('\n')); | ||
process.stderr.write(formatMessage(completion.output)); | ||
return completion.output; | ||
}; |
Empty file.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import { execaCommand } from 'execa'; | ||
import { readFile, writeFile } from 'fs/promises'; | ||
import { afterAll, describe, expect, it } from 'vitest'; | ||
import { removeBackticks } from '../../helpers/remove-backticks'; | ||
|
||
const integrationTestPath = 'src/tests/integration'; | ||
|
||
describe('cli', () => { | ||
it('should run with mock LLM', async () => { | ||
// Write the test file using the mock LLM record | ||
const mockLlmRecordFile = 'test/fixtures/add.json'; | ||
const mockLlmRecordFileContents = await readFile( | ||
mockLlmRecordFile, | ||
'utf-8' | ||
); | ||
const jsonLlmRecording = JSON.parse(mockLlmRecordFileContents.toString()); | ||
|
||
const testContents = jsonLlmRecording.completions[1].output; | ||
await writeFile( | ||
`${integrationTestPath}/add.test.ts`, | ||
removeBackticks(testContents) | ||
); | ||
|
||
// Execute the CLI command | ||
const result = await execaCommand( | ||
`USE_MOCK_LLM=true MOCK_LLM_RECORD_FILE=test/fixtures/add.json jiti ./src/cli.ts ${integrationTestPath}/add.ts -f ${integrationTestPath}/add.test.ts -t "npm run test:all -- add"`, | ||
{ | ||
input: '\x03', | ||
shell: process.env.SHELL || true, | ||
} | ||
); | ||
|
||
const output = result.stdout; | ||
|
||
// Check the output | ||
expect(output).toContain('add is not a function'); | ||
expect(output).toContain('Generating code...'); | ||
expect(output).toContain('Updated code'); | ||
expect(output).toContain('Running tests...'); | ||
expect(output).toContain(`6 passed`); | ||
expect(output).toContain('All tests passed!'); | ||
}); | ||
|
||
afterAll(async () => { | ||
await writeFile(`${integrationTestPath}/add.ts`, ''); | ||
await writeFile(`${integrationTestPath}/add.test.ts`, ''); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { execaCommand } from 'execa'; | ||
import { lstat, writeFile } from 'fs/promises'; | ||
import { beforeAll, describe, expect, it } from 'vitest'; | ||
|
||
const checkConfigFileExists = async () => { | ||
return await lstat(`${process.env.HOME}/.micro-agent`) | ||
.then(() => true) | ||
.catch(() => false); | ||
}; | ||
|
||
describe('interactive cli', () => { | ||
beforeAll(async () => { | ||
const configFileExists = await checkConfigFileExists(); | ||
if (!configFileExists) { | ||
await writeFile( | ||
`${process.env.HOME}/.micro-agent`, | ||
'OPENAI_KEY=sk-1234567890abcdef1234567890abcdef' | ||
); | ||
} | ||
}); | ||
it('should start interactive mode with an intro', async () => { | ||
const result = await execaCommand('jiti ./src/cli.ts', { | ||
input: '\x03', | ||
shell: process.env.SHELL || true, | ||
}); | ||
|
||
const output = result.stdout; | ||
|
||
expect(output).toContain('🦾 Micro Agent'); | ||
}); | ||
|
||
it('should ask for an OpenAI key if not set', async () => { | ||
// Rename the config file to simulate a fresh install | ||
await execaCommand('mv ~/.micro-agent ~/.micro-agent.bak', { | ||
shell: process.env.SHELL || true, | ||
}); | ||
const result = await execaCommand('jiti ./src/cli.ts', { | ||
input: '\x03', | ||
shell: process.env.SHELL || true, | ||
}); | ||
|
||
const output = result.stdout; | ||
|
||
expect(output).toContain('Welcome newcomer! What is your OpenAI key?'); | ||
|
||
// Restore the config file | ||
await execaCommand('mv ~/.micro-agent.bak ~/.micro-agent', { | ||
shell: process.env.SHELL || true, | ||
}); | ||
}); | ||
|
||
it('should ask for a prompt', async () => { | ||
const result = await execaCommand('jiti ./src/cli.ts', { | ||
input: '\x03', | ||
shell: process.env.SHELL || true, | ||
}); | ||
|
||
const output = result.stdout; | ||
|
||
expect(output).toContain('What would you like to do?'); | ||
}); | ||
}); |
Oops, something went wrong.