diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9059ed2..af23970 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -40,6 +40,8 @@ jobs: run: pnpm build - name: Test + env: + OPENAI_KEY: ${{ secrets.OPENAI_KEY }} run: | pnpm test pnpm --use-node-version=14.21.3 test diff --git a/tests/index.ts b/tests/index.ts index 4051fea..d516409 100644 --- a/tests/index.ts +++ b/tests/index.ts @@ -1,5 +1,6 @@ import { describe } from 'manten'; describe('aicommits', ({ runTestSuite }) => { + runTestSuite(import('./specs/cli.js')); runTestSuite(import('./specs/config.js')); }); diff --git a/tests/specs/cli.ts b/tests/specs/cli.ts new file mode 100644 index 0000000..abf3b44 --- /dev/null +++ b/tests/specs/cli.ts @@ -0,0 +1,76 @@ +import { testSuite, expect } from 'manten'; +import { createFixture } from 'fs-fixture'; +import { createAicommits, createGit } from '../utils.js'; + +const { OPENAI_KEY } = process.env; +if (!OPENAI_KEY) { + throw new Error('process.env.OPENAI_KEY is necessary to run these tests'); +} + +export default testSuite(({ describe }) => { + if (process.platform === 'win32') { + // https://github.com/nodejs/node/issues/31409 + console.warn('Skipping tests on Windows because Node.js spawn cant open TTYs'); + return; + } + + describe('CLI', async ({ test }) => { + const fixture = await createFixture({ + 'data.json': JSON.stringify({ + firstName: 'Hiroki', + }), + }); + + const aicommits = createAicommits({ + cwd: fixture.path, + home: fixture.path, + }); + + await test('Fails on non-Git project', async () => { + const { stdout, exitCode } = await aicommits([], { reject: false }); + expect(exitCode).toBe(1); + expect(stdout).toMatch('The current directory must be a Git repository!'); + }); + + const git = await createGit(fixture.path); + + await test('Fails on no staged files', async () => { + const { stdout, exitCode } = await aicommits([], { reject: false }); + expect(exitCode).toBe(1); + expect(stdout).toMatch('No staged changes found. Make sure to stage your changes with `git add`.'); + }); + + await test('Commits', async () => { + await git('add', ['data.json']); + + await aicommits([ + 'config', + 'set', + `OPENAI_KEY=${OPENAI_KEY}`, + ]); + + const statusBefore = await git('status', ['--porcelain', '--untracked-files=no']); + expect(statusBefore.stdout).toBe('A data.json'); + + const committing = aicommits(); + committing.stdout!.on('data', (buffer) => { + const data = buffer.toString(); + + if (data.match('Yes /')) { + committing.stdin!.write('y'); + committing.stdin!.end(); + } + }); + + await committing; + + const statusAfter = await git('status', ['--porcelain', '--untracked-files=no']); + expect(statusAfter.stdout).toBe(''); + + const { stdout } = await git('log', ['--oneline']); + console.log('Commited with:', stdout); + }); + + await fixture.rm(); + }); +}); diff --git a/tests/specs/config.ts b/tests/specs/config.ts index a571ef1..1e04c03 100644 --- a/tests/specs/config.ts +++ b/tests/specs/config.ts @@ -2,26 +2,19 @@ import fs from 'fs/promises'; import path from 'path'; import { testSuite, expect } from 'manten'; import { createFixture } from 'fs-fixture'; -import { execaNode } from 'execa'; - -const aicommitsPath = path.resolve('./dist/cli.mjs'); +import { createAicommits } from '../utils.js'; export default testSuite(({ describe }) => { describe('config', async ({ test }) => { const fixture = await createFixture(); - const env = { - // Linux - HOME: fixture.path, - - // Windows - USERPROFILE: fixture.path, - }; + const aicommits = createAicommits({ + home: fixture.path, + }); const configPath = path.join(fixture.path, '.aicommits'); const openAiToken = 'OPENAI_KEY=sk-abc'; test('set unknown config file', async () => { - const { stderr } = await execaNode(aicommitsPath, ['config', 'set', 'UNKNOWN=1'], { - env, + const { stderr } = await aicommits(['config', 'set', 'UNKNOWN=1'], { reject: false, }); @@ -29,8 +22,7 @@ export default testSuite(({ describe }) => { }); test('set invalid OPENAI_KEY', async () => { - const { stderr } = await execaNode(aicommitsPath, ['config', 'set', 'OPENAI_KEY=abc'], { - env, + const { stderr } = await aicommits(['config', 'set', 'OPENAI_KEY=abc'], { reject: false, }); @@ -38,14 +30,14 @@ export default testSuite(({ describe }) => { }); await test('set config file', async () => { - await execaNode(aicommitsPath, ['config', 'set', openAiToken], { env }); + await aicommits(['config', 'set', openAiToken]); const configFile = await fs.readFile(configPath, 'utf8'); expect(configFile).toMatch(openAiToken); }); await test('get config file', async () => { - const { stdout } = await execaNode(aicommitsPath, ['config', 'get', 'OPENAI_KEY'], { env }); + const { stdout } = await aicommits(['config', 'get', 'OPENAI_KEY']); expect(stdout).toBe(openAiToken); }); @@ -53,8 +45,7 @@ export default testSuite(({ describe }) => { await test('reading unknown config', async () => { await fs.appendFile(configPath, 'UNKNOWN=1'); - const { stdout, stderr } = await execaNode(aicommitsPath, ['config', 'get', 'UNKNOWN'], { - env, + const { stdout, stderr } = await aicommits(['config', 'get', 'UNKNOWN'], { reject: false, }); diff --git a/tests/utils.ts b/tests/utils.ts new file mode 100644 index 0000000..3e47925 --- /dev/null +++ b/tests/utils.ts @@ -0,0 +1,63 @@ +import path from 'path'; +import { execa, execaNode, type Options } from 'execa'; + +const aicommitsPath = path.resolve('./dist/cli.mjs'); + +export const createAicommits = ({ + cwd, + home, +}: { + cwd?: string; + home: string; +}) => { + const homeEnv = { + HOME: home, // Linux + USERPROFILE: home, // Windows + }; + + return ( + args?: string[], + options?: Options, + ) => execaNode(aicommitsPath, args, { + ...options, + cwd, + extendEnv: false, + env: { + ...homeEnv, + ...options?.env, + }, + + // Block tsx nodeOptions + nodeOptions: [], + }); +}; + +export const createGit = async (cwd: string) => { + const git = ( + command: string, + args?: string[], + options?: Options, + ) => ( + execa( + 'git', + [command, ...(args || [])], + { + cwd, + ...options, + }, + ) + ); + + await git( + 'init', + [ + // In case of different default branch name + '--initial-branch=master', + ], + ); + + await git('config', ['user.name', 'name']); + await git('config', ['user.email', 'email']); + + return git; +};