Skip to content
Merged
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
3 changes: 3 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
131 changes: 131 additions & 0 deletions test/commit-message-fixtures.test.js
Original file line number Diff line number Diff line change
@@ -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 = [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@devin Don't setup API parameter in test file.
Our goal is to test the behavior of index.js.

{
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)
})
})
23 changes: 23 additions & 0 deletions test/fixtures/array/chunk.js
Original file line number Diff line number Diff line change
@@ -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
}
24 changes: 24 additions & 0 deletions test/fixtures/object/pick.js
Original file line number Diff line number Diff line change
@@ -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
}
15 changes: 15 additions & 0 deletions test/fixtures/string/camelCase.js
Original file line number Diff line number Diff line change
@@ -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())
}
33 changes: 33 additions & 0 deletions utils/validateTestFixtureCommitMessage.js
Original file line number Diff line number Diff line change
@@ -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
}
53 changes: 53 additions & 0 deletions utils/validateTestFixtureCommitMessage.test.js
Original file line number Diff line number Diff line change
@@ -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)
})
})