Skip to content

Commit 5f3912f

Browse files
PureWeenCopilot
andcommitted
test: add coverage for local repo ID format, validation errors, and .git detection
- AddRepositoryFromLocal_LocalRepoId_HasExpectedFormat: verifies the '{baseId}-local-{hexhash}' ID pattern when URL repo already exists - EnsureRepoClone_SkipsCloneForNonBareRepo_WithGitDirectory: structural guard that .git dir AND .git file are checked - AddRepositoryFromLocal_ValidationErrors_ThrowDescriptiveExceptions: verifies descriptive errors for missing folder, non-git, and no origin Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 7987deb commit 5f3912f

1 file changed

Lines changed: 112 additions & 0 deletions

File tree

PolyPilot.Tests/AddExistingRepoTests.cs

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,118 @@ private static void ForceDeleteDirectory(string path)
497497
Directory.Delete(path, true);
498498
}
499499

500+
[Fact]
501+
public async Task AddRepositoryFromLocal_LocalRepoId_HasExpectedFormat()
502+
{
503+
// The local repo ID should follow the pattern "{baseId}-local-{pathHash}"
504+
// where pathHash is a hex-encoded hash of the normalized path.
505+
var tempDir = Path.Combine(Path.GetTempPath(), $"local-id-format-test-{Guid.NewGuid():N}");
506+
var testBaseDir = Path.Combine(Path.GetTempPath(), $"rmtest-{Guid.NewGuid():N}");
507+
Directory.CreateDirectory(tempDir);
508+
Directory.CreateDirectory(testBaseDir);
509+
try
510+
{
511+
var remoteUrl = "https://github.com/test-owner/id-format-test.git";
512+
513+
await RunProcess("git", "init", tempDir);
514+
await RunProcess("git", "-C", tempDir, "config", "user.email", "test@test.com");
515+
await RunProcess("git", "-C", tempDir, "config", "user.name", "Test");
516+
await RunProcess("git", "-C", tempDir, "commit", "--allow-empty", "-m", "init");
517+
await RunProcess("git", "-C", tempDir, "remote", "add", "origin", remoteUrl);
518+
519+
var rm = new RepoManager();
520+
RepoManager.SetBaseDirForTesting(testBaseDir);
521+
try
522+
{
523+
// Pre-create a URL-based repo so the local one gets a distinct ID
524+
var urlId = RepoManager.RepoIdFromUrl(remoteUrl);
525+
var barePath = Path.Combine(testBaseDir, "repos", $"{urlId}.git");
526+
Directory.CreateDirectory(barePath);
527+
var state = new RepositoryState();
528+
state.Repositories.Add(new RepositoryInfo
529+
{
530+
Id = urlId, Name = "id-format-test",
531+
Url = remoteUrl, BareClonePath = barePath, AddedAt = DateTime.UtcNow
532+
});
533+
File.WriteAllText(Path.Combine(testBaseDir, "repos.json"),
534+
System.Text.Json.JsonSerializer.Serialize(state));
535+
rm.Load();
536+
537+
var localRepo = await rm.AddRepositoryFromLocalAsync(tempDir);
538+
539+
// ID should match pattern: baseId-local-HEXHASH
540+
Assert.Matches(@"^test-owner-id-format-test-local-[0-9a-f]{8}$", localRepo.Id);
541+
}
542+
finally { RepoManager.SetBaseDirForTesting(TestSetup.TestBaseDir); }
543+
}
544+
finally
545+
{
546+
ForceDeleteDirectory(tempDir);
547+
ForceDeleteDirectory(testBaseDir);
548+
}
549+
}
550+
551+
[Fact]
552+
public void EnsureRepoClone_SkipsCloneForNonBareRepo_WithGitDirectory()
553+
{
554+
// EnsureRepoCloneInCurrentRootAsync should detect a .git directory
555+
// and skip clone management for repos added via "Existing Folder".
556+
// This is a structural test that verifies the guard exists.
557+
var sourceFile = File.ReadAllText(Path.Combine(GetRepoRoot(), "PolyPilot", "Services", "RepoManager.cs"));
558+
var methodBody = ExtractMethodBody(sourceFile, "EnsureRepoCloneInCurrentRootAsync");
559+
560+
// Must check for both .git directory and .git file (worktree checkout)
561+
Assert.Contains("Directory.Exists(Path.Combine(repo.BareClonePath, \".git\"))", methodBody);
562+
Assert.Contains("File.Exists(Path.Combine(repo.BareClonePath, \".git\"))", methodBody);
563+
}
564+
565+
[Fact]
566+
public async Task AddRepositoryFromLocal_ValidationErrors_ThrowDescriptiveExceptions()
567+
{
568+
var nonExistent = Path.Combine(Path.GetTempPath(), $"does-not-exist-{Guid.NewGuid():N}");
569+
var notGit = Path.Combine(Path.GetTempPath(), $"not-git-{Guid.NewGuid():N}");
570+
var noOrigin = Path.Combine(Path.GetTempPath(), $"no-origin-{Guid.NewGuid():N}");
571+
var testBaseDir = Path.Combine(Path.GetTempPath(), $"rmtest-{Guid.NewGuid():N}");
572+
Directory.CreateDirectory(notGit);
573+
Directory.CreateDirectory(noOrigin);
574+
Directory.CreateDirectory(testBaseDir);
575+
try
576+
{
577+
// Initialize noOrigin as git repo but without origin remote
578+
await RunProcess("git", "init", noOrigin);
579+
await RunProcess("git", "-C", noOrigin, "config", "user.email", "test@test.com");
580+
await RunProcess("git", "-C", noOrigin, "config", "user.name", "Test");
581+
await RunProcess("git", "-C", noOrigin, "commit", "--allow-empty", "-m", "init");
582+
583+
var rm = new RepoManager();
584+
RepoManager.SetBaseDirForTesting(testBaseDir);
585+
try
586+
{
587+
// Non-existent folder
588+
var ex1 = await Assert.ThrowsAsync<InvalidOperationException>(
589+
() => rm.AddRepositoryFromLocalAsync(nonExistent));
590+
Assert.Contains("not found", ex1.Message, StringComparison.OrdinalIgnoreCase);
591+
592+
// Folder that isn't a git repo
593+
var ex2 = await Assert.ThrowsAsync<InvalidOperationException>(
594+
() => rm.AddRepositoryFromLocalAsync(notGit));
595+
Assert.Contains("not a git repository", ex2.Message, StringComparison.OrdinalIgnoreCase);
596+
597+
// Git repo without origin remote
598+
var ex3 = await Assert.ThrowsAsync<InvalidOperationException>(
599+
() => rm.AddRepositoryFromLocalAsync(noOrigin));
600+
Assert.Contains("origin", ex3.Message, StringComparison.OrdinalIgnoreCase);
601+
}
602+
finally { RepoManager.SetBaseDirForTesting(TestSetup.TestBaseDir); }
603+
}
604+
finally
605+
{
606+
ForceDeleteDirectory(notGit);
607+
ForceDeleteDirectory(noOrigin);
608+
ForceDeleteDirectory(testBaseDir);
609+
}
610+
}
611+
500612
private static string GetRepoRoot()
501613
{
502614
var dir = new DirectoryInfo(AppContext.BaseDirectory);

0 commit comments

Comments
 (0)