167 lines
5.1 KiB
TypeScript
167 lines
5.1 KiB
TypeScript
/**
|
|
* Tests for getOriginalInstruction
|
|
*/
|
|
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
|
|
// Mock child_process.execFileSync
|
|
vi.mock('node:child_process', () => ({
|
|
execFileSync: vi.fn(),
|
|
}));
|
|
|
|
import { execFileSync } from 'node:child_process';
|
|
const mockExecFileSync = vi.mocked(execFileSync);
|
|
|
|
import { getOriginalInstruction } from '../infra/task/branchList.js';
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
describe('getOriginalInstruction', () => {
|
|
it('should extract instruction from branch entry commit via reflog', () => {
|
|
mockExecFileSync
|
|
.mockReturnValueOnce('last789\nfirst456\nbase123\n')
|
|
.mockReturnValueOnce('takt: 認証機能を追加する\n');
|
|
|
|
const result = getOriginalInstruction('/project', 'main', 'takt/20260128-fix-auth');
|
|
|
|
expect(result).toBe('認証機能を追加する');
|
|
expect(mockExecFileSync).toHaveBeenCalledWith(
|
|
'git',
|
|
['reflog', 'show', '--format=%H', 'takt/20260128-fix-auth'],
|
|
expect.objectContaining({ cwd: '/project', encoding: 'utf-8' }),
|
|
);
|
|
expect(mockExecFileSync).toHaveBeenCalledWith(
|
|
'git',
|
|
['show', '-s', '--format=%s', 'first456'],
|
|
expect.objectContaining({ cwd: '/project', encoding: 'utf-8' }),
|
|
);
|
|
});
|
|
|
|
it('should infer base from refs when reflog is unavailable', () => {
|
|
let developMergeBaseCalls = 0;
|
|
mockExecFileSync.mockImplementation((cmd, args) => {
|
|
if (cmd !== 'git') {
|
|
throw new Error('unexpected command');
|
|
}
|
|
|
|
if (args[0] === 'reflog') {
|
|
throw new Error('reflog unavailable');
|
|
}
|
|
|
|
if (args[0] === 'merge-base' && args[1] === 'main') {
|
|
throw new Error('priority main failed');
|
|
}
|
|
|
|
if (args[0] === 'merge-base' && args[1] === 'origin/main') {
|
|
throw new Error('priority origin/main failed');
|
|
}
|
|
|
|
if (args[0] === 'rev-parse' && args[1] === '--git-common-dir') {
|
|
return '.git\n';
|
|
}
|
|
|
|
if (args[0] === 'for-each-ref') {
|
|
return 'develop\n';
|
|
}
|
|
|
|
if (args[0] === 'merge-base' && args[1] === 'develop') {
|
|
developMergeBaseCalls += 1;
|
|
if (developMergeBaseCalls === 1) {
|
|
return 'base123\n';
|
|
}
|
|
throw new Error('unexpected second develop merge-base');
|
|
}
|
|
|
|
if (args[0] === 'rev-list') {
|
|
return '2\n';
|
|
}
|
|
|
|
if (args[0] === 'log' && args[1] === '--format=%s') {
|
|
return 'takt: Initial implementation\nfollow-up\n';
|
|
}
|
|
|
|
if (args[0] === 'log' && args[1] === '--format=%H\t%s') {
|
|
return 'first456\ttakt: Initial implementation\n';
|
|
}
|
|
|
|
throw new Error(`Unexpected git args: ${args.join(' ')}`);
|
|
});
|
|
|
|
const result = getOriginalInstruction('/project', 'main', 'takt/20260128-fix-auth');
|
|
|
|
expect(result).toBe('Initial implementation');
|
|
expect(mockExecFileSync).toHaveBeenCalledWith(
|
|
'git',
|
|
['for-each-ref', '--format=%(refname:short)', 'refs/heads', 'refs/remotes'],
|
|
expect.objectContaining({ cwd: '/project', encoding: 'utf-8' }),
|
|
);
|
|
expect(mockExecFileSync).toHaveBeenCalledWith(
|
|
'git',
|
|
['merge-base', 'develop', 'takt/20260128-fix-auth'],
|
|
expect.objectContaining({ cwd: '/project', encoding: 'utf-8' }),
|
|
);
|
|
});
|
|
|
|
it('should return empty string when no commits on branch', () => {
|
|
mockExecFileSync
|
|
.mockReturnValueOnce('last789\nfirst456\nbase123\n')
|
|
.mockReturnValueOnce('');
|
|
|
|
const result = getOriginalInstruction('/project', 'main', 'takt/20260128-fix-auth');
|
|
|
|
expect(result).toBe('');
|
|
});
|
|
|
|
it('should return empty string when git command fails', () => {
|
|
mockExecFileSync.mockImplementation(() => {
|
|
throw new Error('not a git repository');
|
|
});
|
|
|
|
const result = getOriginalInstruction('/non-existent', 'main', 'takt/20260128-fix-auth');
|
|
|
|
expect(result).toBe('');
|
|
});
|
|
|
|
it('should handle multi-line commit messages (use only first line)', () => {
|
|
mockExecFileSync
|
|
.mockReturnValueOnce('f00dbabe\ndeadbeef\nbase123\n')
|
|
.mockReturnValueOnce('takt: Fix the login bug\n');
|
|
|
|
const result = getOriginalInstruction('/project', 'main', 'takt/20260128-fix-login');
|
|
|
|
expect(result).toBe('Fix the login bug');
|
|
});
|
|
|
|
it('should return empty string when takt prefix has no content', () => {
|
|
mockExecFileSync
|
|
.mockReturnValueOnce('cafebabe\nbase123\n')
|
|
.mockReturnValueOnce('takt:\n');
|
|
|
|
const result = getOriginalInstruction('/project', 'main', 'takt/20260128-task');
|
|
|
|
expect(result).toBe('');
|
|
});
|
|
|
|
it('should return instruction text when takt prefix has content', () => {
|
|
mockExecFileSync
|
|
.mockReturnValueOnce('beadface\nbase123\n')
|
|
.mockReturnValueOnce('takt: add search feature\n');
|
|
|
|
const result = getOriginalInstruction('/project', 'main', 'takt/20260128-task');
|
|
|
|
expect(result).toBe('add search feature');
|
|
});
|
|
|
|
it('should return original subject when branch entry commit has no takt prefix', () => {
|
|
mockExecFileSync
|
|
.mockReturnValueOnce('last789\nfirst456\nbase123\n')
|
|
.mockReturnValueOnce('Initial implementation\n');
|
|
|
|
const result = getOriginalInstruction('/project', 'main', 'takt/20260128-fix-auth');
|
|
|
|
expect(result).toBe('Initial implementation');
|
|
});
|
|
});
|