| import { describe, it, expect, beforeEach, afterEach } from 'vitest'; |
| import { |
| readWorktreeMetadata, |
| writeWorktreeMetadata, |
| updateWorktreePRInfo, |
| getWorktreePRInfo, |
| readAllWorktreeMetadata, |
| deleteWorktreeMetadata, |
| type WorktreeMetadata, |
| type WorktreePRInfo, |
| } from '@/lib/worktree-metadata.js'; |
| import fs from 'fs/promises'; |
| import path from 'path'; |
| import os from 'os'; |
|
|
| describe('worktree-metadata.ts', () => { |
| let testProjectPath: string; |
|
|
| beforeEach(async () => { |
| testProjectPath = path.join(os.tmpdir(), `worktree-metadata-test-${Date.now()}`); |
| await fs.mkdir(testProjectPath, { recursive: true }); |
| }); |
|
|
| afterEach(async () => { |
| try { |
| await fs.rm(testProjectPath, { recursive: true, force: true }); |
| } catch { |
| |
| } |
| }); |
|
|
| describe('sanitizeBranchName', () => { |
| |
| it('should sanitize branch names with invalid characters', async () => { |
| const branch = 'feature/test-branch'; |
| const metadata: WorktreeMetadata = { |
| branch, |
| createdAt: new Date().toISOString(), |
| }; |
|
|
| await writeWorktreeMetadata(testProjectPath, branch, metadata); |
| const result = await readWorktreeMetadata(testProjectPath, branch); |
| expect(result).toEqual(metadata); |
| }); |
|
|
| it('should sanitize branch names with Windows invalid characters', async () => { |
| const branch = 'feature:test*branch?'; |
| const metadata: WorktreeMetadata = { |
| branch, |
| createdAt: new Date().toISOString(), |
| }; |
|
|
| await writeWorktreeMetadata(testProjectPath, branch, metadata); |
| const result = await readWorktreeMetadata(testProjectPath, branch); |
| expect(result).toEqual(metadata); |
| }); |
|
|
| it('should sanitize Windows reserved names', async () => { |
| const branch = 'CON'; |
| const metadata: WorktreeMetadata = { |
| branch, |
| createdAt: new Date().toISOString(), |
| }; |
|
|
| await writeWorktreeMetadata(testProjectPath, branch, metadata); |
| const result = await readWorktreeMetadata(testProjectPath, branch); |
| expect(result).toEqual(metadata); |
| }); |
|
|
| it('should handle empty branch name', async () => { |
| const branch = ''; |
| const metadata: WorktreeMetadata = { |
| branch: 'branch', |
| createdAt: new Date().toISOString(), |
| }; |
|
|
| |
| await writeWorktreeMetadata(testProjectPath, branch, metadata); |
| const result = await readWorktreeMetadata(testProjectPath, branch); |
| expect(result).toEqual(metadata); |
| }); |
|
|
| it('should handle branch name that becomes empty after sanitization', async () => { |
| |
| const branch = '///'; |
| const metadata: WorktreeMetadata = { |
| branch: 'branch', |
| createdAt: new Date().toISOString(), |
| }; |
|
|
| await writeWorktreeMetadata(testProjectPath, branch, metadata); |
| const result = await readWorktreeMetadata(testProjectPath, branch); |
| expect(result).toEqual(metadata); |
| }); |
| }); |
|
|
| describe('readWorktreeMetadata', () => { |
| it("should return null when metadata file doesn't exist", async () => { |
| const result = await readWorktreeMetadata(testProjectPath, 'nonexistent-branch'); |
| expect(result).toBeNull(); |
| }); |
|
|
| it('should read existing metadata', async () => { |
| const branch = 'test-branch'; |
| const metadata: WorktreeMetadata = { |
| branch, |
| createdAt: new Date().toISOString(), |
| }; |
|
|
| await writeWorktreeMetadata(testProjectPath, branch, metadata); |
| const result = await readWorktreeMetadata(testProjectPath, branch); |
| expect(result).toEqual(metadata); |
| }); |
|
|
| it('should read metadata with PR info', async () => { |
| const branch = 'pr-branch'; |
| const metadata: WorktreeMetadata = { |
| branch, |
| createdAt: new Date().toISOString(), |
| pr: { |
| number: 123, |
| url: 'https://github.com/owner/repo/pull/123', |
| title: 'Test PR', |
| state: 'OPEN', |
| createdAt: new Date().toISOString(), |
| }, |
| }; |
|
|
| await writeWorktreeMetadata(testProjectPath, branch, metadata); |
| const result = await readWorktreeMetadata(testProjectPath, branch); |
| expect(result).toEqual(metadata); |
| }); |
| }); |
|
|
| describe('writeWorktreeMetadata', () => { |
| it("should create metadata directory if it doesn't exist", async () => { |
| const branch = 'new-branch'; |
| const metadata: WorktreeMetadata = { |
| branch, |
| createdAt: new Date().toISOString(), |
| }; |
|
|
| await writeWorktreeMetadata(testProjectPath, branch, metadata); |
| const result = await readWorktreeMetadata(testProjectPath, branch); |
| expect(result).toEqual(metadata); |
| }); |
|
|
| it('should overwrite existing metadata', async () => { |
| const branch = 'existing-branch'; |
| const metadata1: WorktreeMetadata = { |
| branch, |
| createdAt: new Date().toISOString(), |
| }; |
| const metadata2: WorktreeMetadata = { |
| branch, |
| createdAt: new Date().toISOString(), |
| pr: { |
| number: 456, |
| url: 'https://github.com/owner/repo/pull/456', |
| title: 'Updated PR', |
| state: 'CLOSED', |
| createdAt: new Date().toISOString(), |
| }, |
| }; |
|
|
| await writeWorktreeMetadata(testProjectPath, branch, metadata1); |
| await writeWorktreeMetadata(testProjectPath, branch, metadata2); |
| const result = await readWorktreeMetadata(testProjectPath, branch); |
| expect(result).toEqual(metadata2); |
| }); |
| }); |
|
|
| describe('updateWorktreePRInfo', () => { |
| it("should create new metadata if it doesn't exist", async () => { |
| const branch = 'new-pr-branch'; |
| const prInfo: WorktreePRInfo = { |
| number: 789, |
| url: 'https://github.com/owner/repo/pull/789', |
| title: 'New PR', |
| state: 'OPEN', |
| createdAt: new Date().toISOString(), |
| }; |
|
|
| await updateWorktreePRInfo(testProjectPath, branch, prInfo); |
| const result = await readWorktreeMetadata(testProjectPath, branch); |
| expect(result).not.toBeNull(); |
| expect(result?.branch).toBe(branch); |
| expect(result?.pr).toEqual(prInfo); |
| }); |
|
|
| it('should update existing metadata with PR info', async () => { |
| const branch = 'existing-pr-branch'; |
| const metadata: WorktreeMetadata = { |
| branch, |
| createdAt: new Date().toISOString(), |
| }; |
|
|
| await writeWorktreeMetadata(testProjectPath, branch, metadata); |
|
|
| const prInfo: WorktreePRInfo = { |
| number: 999, |
| url: 'https://github.com/owner/repo/pull/999', |
| title: 'Updated PR', |
| state: 'MERGED', |
| createdAt: new Date().toISOString(), |
| }; |
|
|
| await updateWorktreePRInfo(testProjectPath, branch, prInfo); |
| const result = await readWorktreeMetadata(testProjectPath, branch); |
| expect(result?.pr).toEqual(prInfo); |
| }); |
|
|
| it('should preserve existing metadata when updating PR info', async () => { |
| const branch = 'preserve-branch'; |
| const originalCreatedAt = new Date().toISOString(); |
| const metadata: WorktreeMetadata = { |
| branch, |
| createdAt: originalCreatedAt, |
| }; |
|
|
| await writeWorktreeMetadata(testProjectPath, branch, metadata); |
|
|
| const prInfo: WorktreePRInfo = { |
| number: 111, |
| url: 'https://github.com/owner/repo/pull/111', |
| title: 'PR', |
| state: 'OPEN', |
| createdAt: new Date().toISOString(), |
| }; |
|
|
| await updateWorktreePRInfo(testProjectPath, branch, prInfo); |
| const result = await readWorktreeMetadata(testProjectPath, branch); |
| expect(result?.createdAt).toBe(originalCreatedAt); |
| expect(result?.pr).toEqual(prInfo); |
| }); |
| }); |
|
|
| describe('getWorktreePRInfo', () => { |
| it("should return null when metadata doesn't exist", async () => { |
| const result = await getWorktreePRInfo(testProjectPath, 'nonexistent'); |
| expect(result).toBeNull(); |
| }); |
|
|
| it('should return null when metadata exists but has no PR info', async () => { |
| const branch = 'no-pr-branch'; |
| const metadata: WorktreeMetadata = { |
| branch, |
| createdAt: new Date().toISOString(), |
| }; |
|
|
| await writeWorktreeMetadata(testProjectPath, branch, metadata); |
| const result = await getWorktreePRInfo(testProjectPath, branch); |
| expect(result).toBeNull(); |
| }); |
|
|
| it('should return PR info when it exists', async () => { |
| const branch = 'has-pr-branch'; |
| const prInfo: WorktreePRInfo = { |
| number: 222, |
| url: 'https://github.com/owner/repo/pull/222', |
| title: 'Has PR', |
| state: 'OPEN', |
| createdAt: new Date().toISOString(), |
| }; |
|
|
| await updateWorktreePRInfo(testProjectPath, branch, prInfo); |
| const result = await getWorktreePRInfo(testProjectPath, branch); |
| expect(result).toEqual(prInfo); |
| }); |
| }); |
|
|
| describe('readAllWorktreeMetadata', () => { |
| it("should return empty map when worktrees directory doesn't exist", async () => { |
| const result = await readAllWorktreeMetadata(testProjectPath); |
| expect(result.size).toBe(0); |
| }); |
|
|
| it('should return empty map when worktrees directory is empty', async () => { |
| const worktreesDir = path.join(testProjectPath, '.automaker', 'worktrees'); |
| await fs.mkdir(worktreesDir, { recursive: true }); |
|
|
| const result = await readAllWorktreeMetadata(testProjectPath); |
| expect(result.size).toBe(0); |
| }); |
|
|
| it('should read all worktree metadata', async () => { |
| const branch1 = 'branch-1'; |
| const branch2 = 'branch-2'; |
| const metadata1: WorktreeMetadata = { |
| branch: branch1, |
| createdAt: new Date().toISOString(), |
| }; |
| const metadata2: WorktreeMetadata = { |
| branch: branch2, |
| createdAt: new Date().toISOString(), |
| pr: { |
| number: 333, |
| url: 'https://github.com/owner/repo/pull/333', |
| title: 'PR 3', |
| state: 'OPEN', |
| createdAt: new Date().toISOString(), |
| }, |
| }; |
|
|
| await writeWorktreeMetadata(testProjectPath, branch1, metadata1); |
| await writeWorktreeMetadata(testProjectPath, branch2, metadata2); |
|
|
| const result = await readAllWorktreeMetadata(testProjectPath); |
| expect(result.size).toBe(2); |
| expect(result.get(branch1)).toEqual(metadata1); |
| expect(result.get(branch2)).toEqual(metadata2); |
| }); |
|
|
| it('should skip directories without worktree.json', async () => { |
| const worktreesDir = path.join(testProjectPath, '.automaker', 'worktrees'); |
| const emptyDir = path.join(worktreesDir, 'empty-dir'); |
| await fs.mkdir(emptyDir, { recursive: true }); |
|
|
| const branch = 'valid-branch'; |
| const metadata: WorktreeMetadata = { |
| branch, |
| createdAt: new Date().toISOString(), |
| }; |
| await writeWorktreeMetadata(testProjectPath, branch, metadata); |
|
|
| const result = await readAllWorktreeMetadata(testProjectPath); |
| expect(result.size).toBe(1); |
| expect(result.get(branch)).toEqual(metadata); |
| }); |
|
|
| it('should skip files in worktrees directory', async () => { |
| const worktreesDir = path.join(testProjectPath, '.automaker', 'worktrees'); |
| await fs.mkdir(worktreesDir, { recursive: true }); |
| const filePath = path.join(worktreesDir, 'not-a-dir.txt'); |
| await fs.writeFile(filePath, 'content'); |
|
|
| const branch = 'valid-branch'; |
| const metadata: WorktreeMetadata = { |
| branch, |
| createdAt: new Date().toISOString(), |
| }; |
| await writeWorktreeMetadata(testProjectPath, branch, metadata); |
|
|
| const result = await readAllWorktreeMetadata(testProjectPath); |
| expect(result.size).toBe(1); |
| expect(result.get(branch)).toEqual(metadata); |
| }); |
|
|
| it('should skip directories with malformed JSON', async () => { |
| const worktreesDir = path.join(testProjectPath, '.automaker', 'worktrees'); |
| const badDir = path.join(worktreesDir, 'bad-dir'); |
| await fs.mkdir(badDir, { recursive: true }); |
| const badJsonPath = path.join(badDir, 'worktree.json'); |
| await fs.writeFile(badJsonPath, 'not valid json'); |
|
|
| const branch = 'valid-branch'; |
| const metadata: WorktreeMetadata = { |
| branch, |
| createdAt: new Date().toISOString(), |
| }; |
| await writeWorktreeMetadata(testProjectPath, branch, metadata); |
|
|
| const result = await readAllWorktreeMetadata(testProjectPath); |
| expect(result.size).toBe(1); |
| expect(result.get(branch)).toEqual(metadata); |
| }); |
| }); |
|
|
| describe('deleteWorktreeMetadata', () => { |
| it('should delete worktree metadata directory', async () => { |
| const branch = 'to-delete'; |
| const metadata: WorktreeMetadata = { |
| branch, |
| createdAt: new Date().toISOString(), |
| }; |
|
|
| await writeWorktreeMetadata(testProjectPath, branch, metadata); |
| let result = await readWorktreeMetadata(testProjectPath, branch); |
| expect(result).not.toBeNull(); |
|
|
| await deleteWorktreeMetadata(testProjectPath, branch); |
| result = await readWorktreeMetadata(testProjectPath, branch); |
| expect(result).toBeNull(); |
| }); |
|
|
| it("should handle deletion when metadata doesn't exist", async () => { |
| |
| await expect(deleteWorktreeMetadata(testProjectPath, 'nonexistent')).resolves.toBeUndefined(); |
| }); |
| }); |
| }); |
|
|