Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions NGitLab.Mock/Clients/MergeRequestApprovalClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ public MergeRequestApprovals ApproveMergeRequest(MergeRequestApproveRequest requ
throw new NotImplementedException();
}

public void UnapproveMergeRequest()
{
throw new NotImplementedException();
Comment thread
louis-z marked this conversation as resolved.
}

public void ResetApprovals()
{
throw new NotImplementedException();
Expand Down
12 changes: 12 additions & 0 deletions NGitLab.Mock/Clients/MergeRequestClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,13 @@ public IMergeRequestApprovalClient ApprovalClient(long mergeRequestIid)
return new MergeRequestApprovalClient(Context, _projectId.GetValueOrDefault(), mergeRequestIid);
}

public IMergeRequestDraftNoteClient DraftNotes(long mergeRequestIid)
{
AssertProjectId();

return new MergeRequestDraftNoteClient(Context);
}

public Models.MergeRequest Close(long mergeRequestIid)
{
AssertProjectId();
Expand Down Expand Up @@ -729,6 +736,11 @@ public GitLabCollectionResponse<Diff> GetDiffsAsync(long mergeRequestIid)
throw new NotImplementedException();
}

public GitLabCollectionResponse<Diff> GetDiffsAsync(long mergeRequestIid, MergeRequestDiffQuery query)
{
throw new NotImplementedException();
}

public Task<TimeStats> TimeStatsAsync(long mergeRequestIid, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
Expand Down
23 changes: 23 additions & 0 deletions NGitLab.Mock/Clients/MergeRequestDraftNoteClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using NGitLab.Models;

namespace NGitLab.Mock.Clients;

internal sealed class MergeRequestDraftNoteClient : ClientBase, IMergeRequestDraftNoteClient
{
public MergeRequestDraftNoteClient(ClientContext context)
: base(context)
{
}

public IEnumerable<DraftNote> All => throw new NotImplementedException();

public Task<DraftNote> CreateAsync(DraftNoteCreate draftNote, CancellationToken cancellationToken = default)
=> throw new NotImplementedException();

public Task PublishAllAsync(CancellationToken cancellationToken = default)
=> throw new NotImplementedException();
}
12 changes: 11 additions & 1 deletion NGitLab.Tests/Docker/GitLabTestContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ public Group CreateSubgroup(long parentGroupId, string slug, string name = null,
configure?.Invoke(g);
});

public (Project Project, MergeRequest MergeRequest) CreateMergeRequest(Action<MergeRequestCreate> configure = null, Action<ProjectCreate> configureProject = null)
public async Task<(Project Project, MergeRequest MergeRequest)> CreateMergeRequestAsync(Action<MergeRequestCreate> configure = null, Action<ProjectCreate> configureProject = null)
{
var client = Client;
var project = CreateProject(configureProject, initializeWithCommits: true);
Expand Down Expand Up @@ -273,6 +273,16 @@ public Group CreateSubgroup(long parentGroupId, string slug, string name = null,
configure?.Invoke(mergeRequestCreate);
var mr = client.GetMergeRequest(project.Id).Create(mergeRequestCreate);

// GitLab computes diff data and merge status asynchronously after MR creation.
// Wait until the transient states resolve so callers can immediately query diffs or approvals.
var mrClient = client.GetMergeRequest(project.Id);
mr = await RetryUntilAsync(
() => mrClient[mr.Iid],
result => result.DetailedMergeStatus != DetailedMergeStatus.Checking &&
result.DetailedMergeStatus != DetailedMergeStatus.Unchecked &&
result.DetailedMergeStatus != DetailedMergeStatus.Preparing,
TimeSpan.FromSeconds(60)).ConfigureAwait(false);

return (project, mr);
}

Expand Down
45 changes: 45 additions & 0 deletions NGitLab.Tests/MergeRequest/MergeRequestApprovalClientTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System;
using System.Threading.Tasks;
using NGitLab.Tests.Docker;
using NUnit.Framework;

namespace NGitLab.Tests;

public class MergeRequestApprovalClientTests
{
[Test]
[NGitLabRetry]
public async Task ApproveMergeRequest_and_UnapproveMergeRequest_roundtrip()
{
using var context = await GitLabTestContext.CreateAsync();
var (project, mergeRequest) = await context.CreateMergeRequestAsync();
var mrClient = context.Client.GetMergeRequest(project.Id);
var approvalClient = mrClient.ApprovalClient(mergeRequest.Iid);

// Approve
var approvals = approvalClient.ApproveMergeRequest();
Assert.That(approvals, Is.Not.Null);
Assert.That(approvals.Approved, Is.True, "MR should be marked as approved after ApproveMergeRequest");

// Unapprove — should not throw
Assert.DoesNotThrow((Action)(() => approvalClient.UnapproveMergeRequest()));

// After unapproval the approval state should show no approved-by entries
var state = approvalClient.Approvals;
Assert.That(state.ApprovedBy, Is.Empty.Or.Null, "No approvers should remain after unapproving");
}

[Test]
[NGitLabRetry]
public async Task UnapproveMergeRequest_on_unapproved_mr_throws()
{
// Arrange
using var context = await GitLabTestContext.CreateAsync();
var (project, mergeRequest) = await context.CreateMergeRequestAsync();
var approvalClient = context.Client.GetMergeRequest(project.Id).ApprovalClient(mergeRequest.Iid);

// Act/Assert
Assert.That((Action)(() => approvalClient.UnapproveMergeRequest()), Throws.TypeOf<GitLabException>(),
"Unapproving an unapproved MR should throw a GitLabException");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class MergeRequestChangesClientTests
public async Task GetChangesOnMergeRequest()
{
using var context = await GitLabTestContext.CreateAsync();
var (project, mergeRequest) = context.CreateMergeRequest();
var (project, mergeRequest) = await context.CreateMergeRequestAsync();
var mergeRequestClient = context.Client.GetMergeRequest(project.Id);
var mergeRequestChanges = mergeRequestClient.Changes(mergeRequest.Iid);

Expand Down
26 changes: 13 additions & 13 deletions NGitLab.Tests/MergeRequest/MergeRequestClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class MergeRequestClientTests
public async Task Test_merge_request_api()
{
using var context = await GitLabTestContext.CreateAsync();
var (project, mergeRequest) = context.CreateMergeRequest();
var (project, mergeRequest) = await context.CreateMergeRequestAsync();
var mergeRequestClient = context.Client.GetMergeRequest(project.Id);

Assert.That(mergeRequestClient[mergeRequest.Iid].Id, Is.EqualTo(mergeRequest.Id), "Test we can get a merge request by IId");
Expand Down Expand Up @@ -61,7 +61,7 @@ await GitLabTestContext.RetryUntilAsync(
public async Task Test_merge_request_rebase()
{
using var context = await GitLabTestContext.CreateAsync();
var (project, mergeRequest) = context.CreateMergeRequest();
var (project, mergeRequest) = await context.CreateMergeRequestAsync();
var mergeRequestClient = context.Client.GetMergeRequest(project.Id);

// Additional commit in default branch, to create divergence
Expand Down Expand Up @@ -102,7 +102,7 @@ public async Task Test_merge_request_rebase()
public async Task Test_merge_request_rebaseasync_skip_ci()
{
using var context = await GitLabTestContext.CreateAsync();
var (project, mergeRequest) = context.CreateMergeRequest();
var (project, mergeRequest) = await context.CreateMergeRequestAsync();
var mergeRequestClient = context.Client.GetMergeRequest(project.Id);

// Additional commit in default branch, to create divergence
Expand Down Expand Up @@ -144,7 +144,7 @@ public async Task Test_merge_request_rebaseasync_skip_ci()
public async Task Test_merge_request_id_is_not_equal_to_iid()
{
using var context = await GitLabTestContext.CreateAsync();
var (_, mergeRequest) = context.CreateMergeRequest();
var (_, mergeRequest) = await context.CreateMergeRequestAsync();
Assert.That(mergeRequest.Iid, Is.Not.EqualTo(mergeRequest.Id));
}

Expand Down Expand Up @@ -174,7 +174,7 @@ public async Task Test_gitlab_returns_an_error_when_trying_to_create_a_request_w
public async Task Test_merge_request_delete()
{
using var context = await GitLabTestContext.CreateAsync();
var (project, mergeRequest) = context.CreateMergeRequest();
var (project, mergeRequest) = await context.CreateMergeRequestAsync();
var mergeRequestClient = context.Client.GetMergeRequest(project.Id);

mergeRequestClient.Delete(mergeRequest.Iid);
Expand All @@ -194,7 +194,7 @@ public async Task Test_merge_request_approvers()
// https://about.gitlab.com/releases/2021/04/22/gitlab-13-11-released/#removal-of-merge-request-approvers-endpoint-in-favor-of-approval-rules-api
context.IgnoreTestIfGitLabVersionOutOfRange(VersionRange.Parse("[,13.11)"));

var (project, mergeRequest) = context.CreateMergeRequest();
var (project, mergeRequest) = await context.CreateMergeRequestAsync();
var mergeRequestClient = context.Client.GetMergeRequest(project.Id);

var approvalClient = mergeRequestClient.ApprovalClient(mergeRequest.Iid);
Expand Down Expand Up @@ -223,7 +223,7 @@ public async Task Test_merge_request_approvers()
public async Task Test_get_unassigned_merge_requests()
{
using var context = await GitLabTestContext.CreateAsync();
var (project, mergeRequest) = context.CreateMergeRequest();
var (project, mergeRequest) = await context.CreateMergeRequestAsync();
var mergeRequestClient = context.Client.GetMergeRequest(project.Id);

var mergeRequests = mergeRequestClient.Get(new MergeRequestQuery { AssigneeId = QueryAssigneeId.None }).ToList();
Expand All @@ -238,7 +238,7 @@ public async Task Test_get_unassigned_merge_requests()
public async Task Test_get_assigned_merge_requests()
{
using var context = await GitLabTestContext.CreateAsync();
var (project, mergeRequest) = context.CreateMergeRequest();
var (project, mergeRequest) = await context.CreateMergeRequestAsync();
var mergeRequestClient = context.Client.GetMergeRequest(project.Id);
var userId = context.Client.Users.Current.Id;
mergeRequestClient.Update(mergeRequest.Iid, new MergeRequestUpdate { AssigneeId = userId });
Expand All @@ -255,8 +255,8 @@ public async Task Test_get_assigned_merge_requests()
public async Task Test_set_reviewers_merge_requests()
{
using var context = await GitLabTestContext.CreateAsync();
var (project, mergeRequest) = context.CreateMergeRequest();
context.CreateMergeRequest(); // Second MR to verify filter returns only one
var (project, mergeRequest) = await context.CreateMergeRequestAsync();
await context.CreateMergeRequestAsync(); // Second MR to verify filter returns only one
var mergeRequestClient = context.Client.GetMergeRequest(project.Id);
var userId = context.Client.Users.Current.Id;
mergeRequestClient.Update(mergeRequest.Iid, new MergeRequestUpdate { ReviewerIds = new[] { userId } });
Expand All @@ -274,7 +274,7 @@ public async Task Test_set_reviewers_merge_requests()
public async Task Test_cancel_merge_when_pipeline_succeeds()
{
using var context = await GitLabTestContext.CreateAsync();
var (project, mergeRequest) = context.CreateMergeRequest();
var (project, mergeRequest) = await context.CreateMergeRequestAsync();
var mergeRequestClient = context.Client.GetMergeRequest(project.Id);

mergeRequest.MergeWhenPipelineSucceeds = true;
Expand All @@ -287,7 +287,7 @@ public async Task Test_cancel_merge_when_pipeline_succeeds()
public async Task Test_merge_request_versions()
{
using var context = await GitLabTestContext.CreateAsync();
var (project, mergeRequest) = context.CreateMergeRequest();
var (project, mergeRequest) = await context.CreateMergeRequestAsync();
var mergeRequestClient = context.Client.GetMergeRequest(project.Id);

var versions = await GitLabTestContext.RetryUntilAsync(
Expand All @@ -305,7 +305,7 @@ public async Task Test_merge_request_versions()
public async Task Test_merge_request_head_pipeline()
{
using var context = await GitLabTestContext.CreateAsync();
var (project, mergeRequest) = context.CreateMergeRequest();
var (project, mergeRequest) = await context.CreateMergeRequestAsync();
var sourceProjectId = await context.Client.Projects.GetByIdAsync(mergeRequest.SourceProjectId, new SingleProjectQuery());
JobTests.AddGitLabCiFile(context.Client, sourceProjectId, branch: mergeRequest.SourceBranch);
var mergeRequestClient = context.Client.GetMergeRequest(project.Id);
Expand Down
13 changes: 7 additions & 6 deletions NGitLab.Tests/MergeRequest/MergeRequestCommentsClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class MergeRequestCommentsClientTests
public async Task AddCommentToMergeRequest_DeprecatedApi()
{
using var context = await GitLabTestContext.CreateAsync();
var (project, mergeRequest) = context.CreateMergeRequest();
var (project, mergeRequest) = await context.CreateMergeRequestAsync();
var mergeRequestClient = context.Client.GetMergeRequest(project.Id);
var mergeRequestComments = mergeRequestClient.Comments(mergeRequest.Iid);

Expand All @@ -33,7 +33,7 @@ public async Task AddCommentToMergeRequest_DeprecatedApi()
public async Task AddEditCommentToMergeRequest()
{
using var context = await GitLabTestContext.CreateAsync();
var (project, mergeRequest) = context.CreateMergeRequest();
var (project, mergeRequest) = await context.CreateMergeRequestAsync();
var mergeRequestClient = context.Client.GetMergeRequest(project.Id);
var mergeRequestComments = mergeRequestClient.Comments(mergeRequest.Iid);

Expand All @@ -56,13 +56,14 @@ public async Task AddEditCommentToMergeRequest()
Assert.That(editedComment.Body, Is.EqualTo(commentMessageEdit));
Assert.That(editedComment.CreatedAt, Is.EqualTo(createdAt));

// Get all
var comments = mergeRequestComments.All.ToArray();
// 'All' returns all notes, including system notes that GitLab auto-generates (e.g. "changed the merge status to can be merged" or similar state-change events).
// Get all non-system notes
var comments = mergeRequestComments.All.Where(c => !c.System).ToArray();
Assert.That(comments, Is.Not.Empty);

// Delete
mergeRequestComments.Delete(comment.Id);
comments = mergeRequestComments.All.ToArray();
comments = mergeRequestComments.All.Where(c => !c.System).ToArray();
Assert.That(comments, Is.Empty);
}

Expand All @@ -71,7 +72,7 @@ public async Task AddEditCommentToMergeRequest()
public async Task AddCommentToMergeRequestOnArchivedProject()
{
using var context = await GitLabTestContext.CreateAsync();
var (project, mergeRequest) = context.CreateMergeRequest();
var (project, mergeRequest) = await context.CreateMergeRequestAsync();
var mergeRequestClient = context.Client.GetMergeRequest(project.Id);
var mergeRequestComments = mergeRequestClient.Comments(mergeRequest.Iid);

Expand Down
60 changes: 60 additions & 0 deletions NGitLab.Tests/MergeRequest/MergeRequestDiffsClientTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using System.Linq;
using System.Threading.Tasks;
using NGitLab.Models;
using NGitLab.Tests.Docker;
using NUnit.Framework;

namespace NGitLab.Tests;

public class MergeRequestDiffsClientTests
{
[Test]
[NGitLabRetry]
public async Task GetDiffsAsync_returns_changed_files()
{
using var context = await GitLabTestContext.CreateAsync();
var (project, mergeRequest) = await context.CreateMergeRequestAsync();
var mrClient = context.Client.GetMergeRequest(project.Id);

var diffs = await mrClient.GetDiffsAsync(mergeRequest.Iid, query: null).ToListAsync();

Assert.That(diffs, Has.Count.GreaterThan(0), "At least one diff should be returned");
Assert.That(diffs[0].OldPath, Is.Not.Null.And.Not.Empty);
Assert.That(diffs[0].NewPath, Is.Not.Null.And.Not.Empty);
}

[Test]
[NGitLabRetry]
public async Task GetDiffsAsync_with_unidiff_returns_changed_files()
{
using var context = await GitLabTestContext.CreateAsync();
var (project, mergeRequest) = await context.CreateMergeRequestAsync();
var mrClient = context.Client.GetMergeRequest(project.Id);

var query = new MergeRequestDiffQuery { Unidiff = true };
var diffs = await mrClient.GetDiffsAsync(mergeRequest.Iid, query).ToListAsync();

Assert.That(diffs, Has.Count.GreaterThan(0), "At least one diff should be returned with unidiff=true");
Assert.That(diffs[0].OldPath, Is.Not.Null.And.Not.Empty);
Assert.That(diffs[0].NewPath, Is.Not.Null.And.Not.Empty);
}

[Test]
[NGitLabRetry]
public async Task GetDiffsAsync_unidiff_format_starts_with_unified_diff_header()
{
using var context = await GitLabTestContext.CreateAsync();
var (project, mergeRequest) = await context.CreateMergeRequestAsync();
var mrClient = context.Client.GetMergeRequest(project.Id);

var plainDiffs = await mrClient.GetDiffsAsync(mergeRequest.Iid, query: null).ToListAsync();
var unifiedDiffs = await mrClient.GetDiffsAsync(mergeRequest.Iid, new MergeRequestDiffQuery { Unidiff = true }).ToListAsync();

Assert.That(plainDiffs, Has.Count.EqualTo(unifiedDiffs.Count), "Both queries should return the same number of files");

// Unified diff format uses "--- a/..." / "+++ b/..." headers; the plain GitLab format does not.
var firstUnified = unifiedDiffs[0].Difference;
Assert.That(firstUnified, Does.StartWith("---").Or.StartWith("diff --git"),
"Unified diff content should start with standard unified-diff markers");
}
}
Loading
Loading