diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4a98fec..7b734fc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,5 +13,8 @@ jobs: timezoneLinux: 'Asia/Tokyo' - uses: actions/checkout@v4 - uses: ./.github/actions/prepare + - name: Create .env file + run: | + echo "OPENAI_API_KEY=${{ secrets.OPENAI_API_KEY }}" > .env - name: Test run: pnpm test diff --git a/test/commit-message-fixtures.test.js b/test/commit-message-fixtures.test.js new file mode 100644 index 0000000..1794814 --- /dev/null +++ b/test/commit-message-fixtures.test.js @@ -0,0 +1,131 @@ +import { describe, it, expect, beforeEach } from 'vitest' +import { validateTestFixtureCommitMessage } from '../utils/validateTestFixtureCommitMessage' +import * as path from 'path' +import * as fs from 'fs' +import OpenAI from 'openai' +import dotenv from 'dotenv' + +dotenv.config() + +const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }) + +describe('Test Fixture Commit Message Validation', () => { + beforeEach(() => {}) + + it('should validate commit messages when test fixture files are modified', async () => { + if (!process.env.OPENAI_API_KEY) { + console.warn('Skipping test: No OpenAI API key available') + return + } + + const testFixtureDiff = ` +diff --git a/test/fixtures/array/chunk.js b/test/fixtures/array/chunk.js +index 1234567..abcdefg 100644 +--- a/test/fixtures/array/chunk.js ++++ b/test/fixtures/array/chunk.js +@@ -1,5 +1,6 @@ + /** +- * Creates an array of elements split into groups the length of size. ++ * Creates an array of elements split into groups the length of size. ++ * If array can't be split evenly, the final chunk will be the remaining elements. + * + * @param {Array} array - The array to process + * @param {number} [size=1] - The length of each chunk +` + + const messages = [ + { + role: 'system', + content: + 'You are a helpful assistant. Write the commit message in English. This commit modifies test fixtures, so begin your message with "test:" and include "update fixture for [functionality]".', + }, + { + role: 'user', + content: `Generate a Git commit message based on the following summary: ${testFixtureDiff}\n\nCommit message: `, + }, + ] + + const parameters = { + model: 'gpt-4o', + messages, + n: 1, + temperature: 0, + max_tokens: 50, + } + + const response = await openai.chat.completions.create(parameters) + const message = response.choices[0].message.content.trim() + + const isValidCommitMessage = validateTestFixtureCommitMessage( + message, + 'array', + ) + + expect(isValidCommitMessage).toBe(true) + }) + + it('should reject invalid commit messages for fixture changes', async () => { + if (!process.env.OPENAI_API_KEY) { + console.warn('Skipping test: No OpenAI API key available') + return + } + + const testFixtureDiff = ` +diff --git a/test/fixtures/string/camelCase.js b/test/fixtures/string/camelCase.js +index 1234567..abcdefg 100644 +--- a/test/fixtures/string/camelCase.js ++++ b/test/fixtures/string/camelCase.js +@@ -1,4 +1,5 @@ + /** ++ * Improved documentation. + * Converts string to camel case. + * + * @param {string} string - The string to convert +` + + const messages = [ + { + role: 'system', + content: + 'You are a helpful assistant. Write the commit message in English. This is a bugfix, so begin your message with "fix:".', + }, + { + role: 'user', + content: `Generate a Git commit message based on the following summary: ${testFixtureDiff}\n\nCommit message: `, + }, + ] + + const parameters = { + model: 'gpt-4o', + messages, + n: 1, + temperature: 0, + max_tokens: 50, + } + + const response = await openai.chat.completions.create(parameters) + const message = response.choices[0].message.content.trim() + + const isValidCommitMessage = validateTestFixtureCommitMessage( + message, + 'string', + ) + + expect(isValidCommitMessage).toBe(false) + }) + + it('should recognize test fixture directory changes', () => { + const fixturesPath = path.join(process.cwd(), 'test', 'fixtures') + const exists = fs.existsSync(fixturesPath) + + expect(exists).toBe(true) + + const arrayDirExists = fs.existsSync(path.join(fixturesPath, 'array')) + const objectDirExists = fs.existsSync(path.join(fixturesPath, 'object')) + const stringDirExists = fs.existsSync(path.join(fixturesPath, 'string')) + + expect(arrayDirExists).toBe(true) + expect(objectDirExists).toBe(true) + expect(stringDirExists).toBe(true) + }) +}) diff --git a/test/fixtures/array/chunk.js b/test/fixtures/array/chunk.js new file mode 100644 index 0000000..503fff1 --- /dev/null +++ b/test/fixtures/array/chunk.js @@ -0,0 +1,23 @@ +/** + * Creates an array of elements split into groups the length of size. + * If array can't be split evenly, the final chunk will be the remaining elements. + * + * @param {Array} array - The array to process + * @param {number} [size=1] - The length of each chunk + * @returns {Array} Returns the new array of chunks + */ +export function chunk(array, size = 1) { + const length = array == null ? 0 : array.length + if (!length || size < 1) { + return [] + } + + const result = [] + let index = 0 + + while (index < length) { + result.push(array.slice(index, (index += size))) + } + + return result +} diff --git a/test/fixtures/object/pick.js b/test/fixtures/object/pick.js new file mode 100644 index 0000000..ba747a7 --- /dev/null +++ b/test/fixtures/object/pick.js @@ -0,0 +1,24 @@ +/** + * Creates an object composed of the picked object properties. + * + * @param {Object} object - The source object + * @param {...(string|string[])} [paths] - The property paths to pick + * @returns {Object} Returns the new object + */ +export function pick(object, ...paths) { + const result = {} + + if (object == null) { + return result + } + + const flatPaths = [].concat(...paths) + + flatPaths.forEach((path) => { + if (path in object) { + result[path] = object[path] + } + }) + + return result +} diff --git a/test/fixtures/string/camelCase.js b/test/fixtures/string/camelCase.js new file mode 100644 index 0000000..8576285 --- /dev/null +++ b/test/fixtures/string/camelCase.js @@ -0,0 +1,15 @@ +/** + * Converts string to camel case. + * + * @param {string} string - The string to convert + * @returns {string} Returns the camel cased string + */ +export function camelCase(string) { + if (!string) { + return '' + } + + return string + .replace(/[^a-zA-Z0-9]+(.)/g, (_, chr) => chr.toUpperCase()) + .replace(/^[A-Z]/, (c) => c.toLowerCase()) +} diff --git a/utils/validateTestFixtureCommitMessage.js b/utils/validateTestFixtureCommitMessage.js new file mode 100644 index 0000000..30bbba3 --- /dev/null +++ b/utils/validateTestFixtureCommitMessage.js @@ -0,0 +1,33 @@ +/** + * Validates that commit messages for test fixture changes follow the required format + * Format: "test: update fixture for [functionality]" + * + * @param {string} message - The commit message to validate + * @param {string} [fixtureType] - Optional fixture type to validate against + * @returns {boolean} Returns true if the message is valid, false otherwise + */ +export function validateTestFixtureCommitMessage(message, fixtureType = null) { + if (!message) { + return false + } + + const basicFormatRegex = /^test(\(fixtures\))?:/i + if (!basicFormatRegex.test(message)) { + return false + } + + const updateFixtureRegex = + /update fixture|add fixture|modify fixture|fixture change/i + if (!updateFixtureRegex.test(message)) { + return false + } + + if ( + fixtureType && + !message.toLowerCase().includes(fixtureType.toLowerCase()) + ) { + return false + } + + return true +} diff --git a/utils/validateTestFixtureCommitMessage.test.js b/utils/validateTestFixtureCommitMessage.test.js new file mode 100644 index 0000000..76ad842 --- /dev/null +++ b/utils/validateTestFixtureCommitMessage.test.js @@ -0,0 +1,53 @@ +import { describe, it, expect } from 'vitest' +import { validateTestFixtureCommitMessage } from './validateTestFixtureCommitMessage' + +describe('validateTestFixtureCommitMessage', () => { + it('should return false for an empty commit message', () => { + expect(validateTestFixtureCommitMessage('')).toBe(false) + }) + + it('should return true for valid test fixture commit messages', () => { + expect( + validateTestFixtureCommitMessage( + 'test: update fixture for array methods', + ), + ).toBe(true) + expect( + validateTestFixtureCommitMessage( + 'test(fixtures): add fixture for object utility', + ), + ).toBe(true) + expect( + validateTestFixtureCommitMessage( + 'test: modify fixture for string camelCase', + ), + ).toBe(true) + }) + + it('should return false for invalid test fixture commit messages', () => { + expect(validateTestFixtureCommitMessage('feat: add new feature')).toBe( + false, + ) + expect(validateTestFixtureCommitMessage('fix: update test fixture')).toBe( + false, + ) + expect(validateTestFixtureCommitMessage('test: fix bug in main code')).toBe( + false, + ) + }) + + it('should validate specific fixture types if provided', () => { + expect( + validateTestFixtureCommitMessage( + 'test: update fixture for array methods', + 'array', + ), + ).toBe(true) + expect( + validateTestFixtureCommitMessage( + 'test: update fixture for string methods', + 'array', + ), + ).toBe(false) + }) +})