| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170 |
- using LibGit2Sharp;
- using RackPeek.Domain.Git;
- using RackPeek.Domain.Git.UseCases;
- namespace Tests.Git;
- public sealed class GitRepositoryAvailabilityTests : IDisposable {
- private readonly string _tempDir;
- private readonly IGitCredentialsProvider _creds =
- new TokenCredentialsProvider("test-user", "test-token");
- public GitRepositoryAvailabilityTests() {
- _tempDir = Path.Combine(
- Path.GetTempPath(),
- "rackpeek-git-tests",
- Guid.NewGuid().ToString());
- Directory.CreateDirectory(_tempDir);
- File.WriteAllText(Path.Combine(_tempDir, "config.yaml"), "");
- }
- public void Dispose() {
- try {
- if (Directory.Exists(_tempDir))
- ForceDelete(_tempDir);
- }
- catch {
- // ignore cleanup issues
- }
- }
- private static void ForceDelete(string path) {
- foreach (var file in Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories)) {
- File.SetAttributes(file, FileAttributes.Normal);
- }
- Directory.Delete(path, true);
- }
- [Fact]
- public void Fresh_Config_Directory_Becomes_Available_After_Construction() {
- // Reproduces the user's bug: GIT_TOKEN is set, container starts on a
- // fresh volume with only config.yaml. Without auto-init, IsAvailable
- // stays false and every git action returns "Git is not available."
- Assert.False(Repository.IsValid(_tempDir),
- "precondition: fresh dir should not be a git repo yet");
- var repo = new LibGit2GitRepository(_tempDir, _creds);
- Assert.True(repo.IsAvailable,
- "after construction with GIT_TOKEN configured, the repo should be initialised " +
- "so the user can immediately add a remote without an explicit Enable Git click");
- }
- [Fact]
- public async Task Add_Remote_Succeeds_On_Fresh_Config_Directory() {
- // End-to-end: simulates the user clicking Add Remote on a brand-new
- // install. Before the fix this returns "Git is not available." even
- // though GIT_TOKEN was set on the container.
- var repo = new LibGit2GitRepository(_tempDir, _creds);
- var useCase = new AddRemoteUseCase(repo);
- var error = await useCase.ExecuteAsync("https://example.com/user/repo.git");
- // The AddRemote fetch will fail (no real remote) but the failure
- // should be a network error, not "Git is not available."
- Assert.DoesNotContain("Git is not available", error ?? string.Empty);
- }
- [Fact]
- public void Existing_Repo_Is_Not_Reinitialised() {
- // Auto-init must be idempotent: pre-existing repos must keep their
- // history. Initialising twice would discard the user's commits.
- Repository.Init(_tempDir);
- using (var seed = new Repository(_tempDir)) {
- File.WriteAllText(Path.Combine(_tempDir, "seed.txt"), "seed");
- Commands.Stage(seed, "seed.txt");
- var sig = new Signature("seed", "seed@test", DateTimeOffset.UtcNow);
- seed.Commit("seed commit", sig, sig);
- }
- var repo = new LibGit2GitRepository(_tempDir, _creds);
- Assert.True(repo.IsAvailable);
- using var verify = new Repository(_tempDir);
- Assert.NotNull(verify.Head.Tip);
- Assert.Equal("seed commit", verify.Head.Tip.MessageShort);
- }
- [Fact]
- public void Missing_Directory_Stays_Unavailable() {
- // Edge case: directory doesn't exist (e.g. misconfigured volume).
- // We must not throw out of the constructor and we must not pretend
- // git is available.
- var missing = Path.Combine(_tempDir, "definitely-not-there");
- var repo = new LibGit2GitRepository(missing, _creds);
- Assert.False(repo.IsAvailable);
- }
- [Fact]
- public void Readonly_Config_Directory_Does_Not_Throw_From_Constructor() {
- // Doc promises the UI will surface "Git configured but config directory
- // is not writable" when init can't run — that requires the constructor
- // to swallow the init failure and set IsAvailable=false rather than
- // crashing the Blazor render that resolves the singleton.
- if (OperatingSystem.IsWindows())
- return; // chmod-style read-only doesn't translate cleanly
- var readOnly = Path.Combine(_tempDir, "readonly");
- Directory.CreateDirectory(readOnly);
- File.WriteAllText(Path.Combine(readOnly, "config.yaml"), "");
- // Strip write permission for owner + group + other (0555).
- File.SetUnixFileMode(readOnly,
- UnixFileMode.UserRead | UnixFileMode.UserExecute |
- UnixFileMode.GroupRead | UnixFileMode.GroupExecute |
- UnixFileMode.OtherRead | UnixFileMode.OtherExecute);
- try {
- var repo = new LibGit2GitRepository(readOnly, _creds);
- Assert.False(repo.IsAvailable,
- "constructor must report unavailable, not throw, so the UI can " +
- "show the documented writability warning");
- }
- finally {
- // Restore write perm so cleanup works.
- File.SetUnixFileMode(readOnly,
- UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute);
- }
- }
- [Fact]
- public async Task Add_Remote_Persists_The_Configured_Origin() {
- // The full happy path that was broken before the fix: construct a repo
- // against a fresh dir, add a remote, confirm it lives on disk so a
- // subsequent push/pull would find it.
- var repo = new LibGit2GitRepository(_tempDir, _creds);
- var useCase = new AddRemoteUseCase(repo);
- var url = "https://example.com/user/repo.git";
- await useCase.ExecuteAsync(url);
- using var verify = new Repository(_tempDir);
- Remote? origin = verify.Network.Remotes["origin"];
- Assert.NotNull(origin);
- Assert.Equal(url, origin!.Url);
- }
- [Fact]
- public void Existing_Repo_With_Remote_Is_Detected_As_Available() {
- // After a container restart we should pick up the previously
- // initialised repo (including its remote) without re-initialising
- // and without losing state.
- Repository.Init(_tempDir);
- using (var seed = new Repository(_tempDir)) {
- seed.Network.Remotes.Add("origin", "https://example.com/user/repo.git");
- }
- var repo = new LibGit2GitRepository(_tempDir, _creds);
- Assert.True(repo.IsAvailable);
- Assert.True(repo.HasRemote());
- }
- }
|