- Add comprehensive health check system with multiple endpoints - Add Prometheus metrics endpoint - Add production logging configurations (5 strategies) - Add complete deployment documentation suite: * QUICKSTART.md - 30-minute deployment guide * DEPLOYMENT_CHECKLIST.md - Printable verification checklist * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference * production-logging.md - Logging configuration guide * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation * README.md - Navigation hub * DEPLOYMENT_SUMMARY.md - Executive summary - Add deployment scripts and automation - Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment - Update README with production-ready features All production infrastructure is now complete and ready for deployment.
239 lines
9.4 KiB
PHP
239 lines
9.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Framework\Mcp\Tools\GitTools;
|
|
|
|
describe('GitTools', function () {
|
|
beforeEach(function () {
|
|
$this->projectRoot = '/home/michael/dev/michaelschiemer';
|
|
$this->gitTools = new GitTools($this->projectRoot);
|
|
});
|
|
|
|
describe('git_status', function () {
|
|
it('returns git status with staged, unstaged, and untracked files', function () {
|
|
$result = $this->gitTools->gitStatus();
|
|
|
|
expect($result)->toHaveKeys(['staged', 'unstaged', 'untracked', 'clean', 'summary']);
|
|
expect($result['staged'])->toBeArray();
|
|
expect($result['unstaged'])->toBeArray();
|
|
expect($result['untracked'])->toBeArray();
|
|
expect($result['summary'])->toHaveKeys(['staged_count', 'unstaged_count', 'untracked_count']);
|
|
expect($result['clean'])->toBeIn([true, false]);
|
|
});
|
|
|
|
it('marks repository as clean when no changes exist', function () {
|
|
// This test assumes a clean repository state
|
|
// In real scenarios, you might need to setup a clean git state first
|
|
$result = $this->gitTools->gitStatus();
|
|
|
|
if ($result['clean']) {
|
|
expect($result['staged'])->toBeEmpty();
|
|
expect($result['unstaged'])->toBeEmpty();
|
|
expect($result['untracked'])->toBeEmpty();
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('git_diff', function () {
|
|
it('returns diff for unstaged changes by default', function () {
|
|
$result = $this->gitTools->gitDiff(staged: false);
|
|
|
|
expect($result)->toHaveKeys(['diff', 'staged', 'file', 'has_changes']);
|
|
expect($result['staged'])->toBeFalse();
|
|
});
|
|
|
|
it('returns diff for staged changes when staged flag is true', function () {
|
|
$result = $this->gitTools->gitDiff(staged: true);
|
|
|
|
expect($result)->toHaveKeys(['diff', 'staged', 'file', 'has_changes']);
|
|
expect($result['staged'])->toBeTrue();
|
|
});
|
|
|
|
it('can get diff for specific file', function () {
|
|
$result = $this->gitTools->gitDiff(staged: false, file: 'README.md');
|
|
|
|
expect($result)->toHaveKey('file');
|
|
expect($result['file'])->toBe('README.md');
|
|
});
|
|
});
|
|
|
|
describe('git_log', function () {
|
|
it('returns recent commit history', function () {
|
|
$result = $this->gitTools->gitLog(limit: 5);
|
|
|
|
expect($result)->toHaveKeys(['commits', 'count', 'limit']);
|
|
expect($result['commits'])->toBeArray();
|
|
expect($result['limit'])->toBe(5);
|
|
|
|
if (! empty($result['commits'])) {
|
|
$commit = $result['commits'][0];
|
|
expect($commit)->toHaveKeys(['hash', 'short_hash', 'author', 'email', 'date', 'message']);
|
|
expect(strlen($commit['hash']))->toBe(40); // Full SHA-1 hash
|
|
expect(strlen($commit['short_hash']))->toBe(7);
|
|
}
|
|
});
|
|
|
|
it('respects the limit parameter', function () {
|
|
$result = $this->gitTools->gitLog(limit: 3);
|
|
|
|
expect($result['count'])->toBeLessThanOrEqual(3);
|
|
expect($result['limit'])->toBe(3);
|
|
});
|
|
});
|
|
|
|
describe('git_branch_info', function () {
|
|
it('returns current branch information', function () {
|
|
$result = $this->gitTools->gitBranchInfo();
|
|
|
|
expect($result)->toHaveKeys(['current_branch', 'local_branches', 'remote_branches', 'total_local', 'total_remote']);
|
|
expect($result['current_branch'])->toBeString();
|
|
expect($result['local_branches'])->toBeArray();
|
|
expect($result['remote_branches'])->toBeArray();
|
|
});
|
|
|
|
it('includes branch counts', function () {
|
|
$result = $this->gitTools->gitBranchInfo();
|
|
|
|
expect($result['total_local'])->toBeInt();
|
|
expect($result['total_remote'])->toBeInt();
|
|
expect($result['total_local'])->toBe(count($result['local_branches']));
|
|
expect($result['total_remote'])->toBe(count($result['remote_branches']));
|
|
});
|
|
});
|
|
|
|
describe('git_changed_files', function () {
|
|
it('returns all changed files with their status', function () {
|
|
$result = $this->gitTools->gitChangedFiles();
|
|
|
|
expect($result)->toHaveKeys(['changes', 'total', 'by_status']);
|
|
expect($result['changes'])->toBeArray();
|
|
expect($result['total'])->toBeInt();
|
|
expect($result['by_status'])->toBeArray();
|
|
});
|
|
|
|
it('includes staged flag for each change', function () {
|
|
$result = $this->gitTools->gitChangedFiles();
|
|
|
|
foreach ($result['changes'] as $change) {
|
|
expect($change)->toHaveKeys(['file', 'status', 'staged']);
|
|
expect($change['staged'])->toBeIn([true, false]);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('git_stash_list', function () {
|
|
it('returns list of stashed changes', function () {
|
|
$result = $this->gitTools->gitStashList();
|
|
|
|
expect($result)->toHaveKeys(['stashes', 'count']);
|
|
expect($result['stashes'])->toBeArray();
|
|
expect($result['count'])->toBe(count($result['stashes']));
|
|
});
|
|
|
|
it('parses stash entries correctly', function () {
|
|
$result = $this->gitTools->gitStashList();
|
|
|
|
foreach ($result['stashes'] as $stash) {
|
|
expect($stash)->toHaveKeys(['ref', 'index', 'message']);
|
|
expect($stash['ref'])->toContain('stash@{');
|
|
expect($stash['index'])->toBeInt();
|
|
expect($stash['message'])->toBeString();
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('git_remote_info', function () {
|
|
it('returns configured remotes', function () {
|
|
$result = $this->gitTools->gitRemoteInfo();
|
|
|
|
expect($result)->toHaveKeys(['remotes', 'count']);
|
|
expect($result['remotes'])->toBeArray();
|
|
expect($result['count'])->toBe(count($result['remotes']));
|
|
});
|
|
|
|
it('includes fetch and push URLs for each remote', function () {
|
|
$result = $this->gitTools->gitRemoteInfo();
|
|
|
|
foreach ($result['remotes'] as $remote) {
|
|
expect($remote)->toHaveKey('name');
|
|
expect($remote['name'])->toBeString();
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('git_conflict_status', function () {
|
|
it('checks for merge/rebase conflicts', function () {
|
|
$result = $this->gitTools->gitConflictStatus();
|
|
|
|
expect($result)->toHaveKeys(['has_conflicts', 'state', 'conflicted_files', 'count']);
|
|
expect($result['has_conflicts'])->toBeIn([true, false]);
|
|
expect($result['state'])->toBeIn(['normal', 'merging', 'rebasing', 'cherry-picking']);
|
|
expect($result['conflicted_files'])->toBeArray();
|
|
expect($result['count'])->toBe(count($result['conflicted_files']));
|
|
});
|
|
|
|
it('returns normal state when no conflicts exist', function () {
|
|
$result = $this->gitTools->gitConflictStatus();
|
|
|
|
// Assuming clean repository state
|
|
if (! $result['has_conflicts']) {
|
|
expect($result['state'])->toBe('normal');
|
|
expect($result['conflicted_files'])->toBeEmpty();
|
|
expect($result['count'])->toBe(0);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('git_generate_commit_message', function () {
|
|
it('generates conventional commit message from staged changes', function () {
|
|
// This test requires staged changes
|
|
$result = $this->gitTools->generateCommitMessage();
|
|
|
|
// If no staged changes, should return error
|
|
if (isset($result['error'])) {
|
|
expect($result['error'])->toContain('No staged changes');
|
|
expect($result)->toHaveKey('suggestion');
|
|
} else {
|
|
// If staged changes exist
|
|
expect($result)->toHaveKeys([
|
|
'suggested_message',
|
|
'type',
|
|
'scope',
|
|
'description',
|
|
'changes_summary',
|
|
'conventional_commit',
|
|
]);
|
|
expect($result['conventional_commit'])->toBeTrue();
|
|
expect($result['type'])->toBeIn(['feat', 'fix', 'docs', 'test', 'chore', 'refactor']);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('error handling', function () {
|
|
it('handles git command failures gracefully', function () {
|
|
// Create GitTools with invalid project root
|
|
$invalidGitTools = new GitTools('/invalid/path/that/does/not/exist');
|
|
|
|
$result = $invalidGitTools->gitStatus();
|
|
|
|
// Should either work (if git falls back to current dir) or return error
|
|
expect($result)->toBeArray();
|
|
});
|
|
});
|
|
|
|
describe('private helper methods', function () {
|
|
it('correctly analyzes diff output', function () {
|
|
// Test through generateCommitMessage which uses analyzeDiff
|
|
$result = $this->gitTools->generateCommitMessage();
|
|
|
|
if (isset($result['changes_summary'])) {
|
|
$summary = $result['changes_summary'];
|
|
expect($summary)->toHaveKeys(['files', 'file_count', 'additions', 'deletions', 'net_change']);
|
|
expect($summary['file_count'])->toBe(count($summary['files']));
|
|
expect($summary['net_change'])->toBe($summary['additions'] - $summary['deletions']);
|
|
}
|
|
});
|
|
});
|
|
});
|