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
2 changes: 1 addition & 1 deletion src/Api/Data/IssueRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ public async Task<bool> ArchiveAsync(string issueId, string archivedBy, Cancella
update,
cancellationToken: cancellationToken);

return result.ModifiedCount > 0;
return result.MatchedCount > 0;
}

/// <inheritdoc />
Expand Down
86 changes: 86 additions & 0 deletions tests/Integration/Handlers/DeleteIssueHandlerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
using Shared.Domain;

namespace IssueManager.Tests.Integration.Handlers;

/// <summary>
/// Integration tests for IssueRepository.ArchiveAsync (soft-delete).
/// Verifies correct behavior when archiving existing, already-archived, and non-existent issues.
/// </summary>
public class DeleteIssueHandlerTests : IAsyncLifetime
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The class/file name suggests these are tests for a delete handler, but the tests (and XML summary) exercise IssueRepository.ArchiveAsync directly and never instantiate/call DeleteIssueHandler. Renaming/moving this test to match what it covers will make test intent and failures easier to understand (e.g., an IssueRepositoryArchiveAsyncTests-style name and/or placement under a repository/data test folder).

Suggested change
public class DeleteIssueHandlerTests : IAsyncLifetime
public class IssueRepositoryArchiveAsyncTests : IAsyncLifetime

Copilot uses AI. Check for mistakes.
{
private const string MONGODB_IMAGE = "mongo:8.0";
private const string TEST_DATABASE = "IssueManagerTestDb";
private readonly MongoDbContainer _mongoContainer;

private IIssueRepository _repository = null!;

public DeleteIssueHandlerTests()
{
_mongoContainer = new MongoDbBuilder()
.WithImage(MONGODB_IMAGE)
.Build();
}

/// <summary>
/// Initializes the test container and repository.
/// </summary>
public async Task InitializeAsync()
{
await _mongoContainer.StartAsync();
var connectionString = _mongoContainer.GetConnectionString();
_repository = new IssueRepository(connectionString, TEST_DATABASE);
}

/// <summary>
/// Disposes the test container.
/// </summary>
public async Task DisposeAsync()
{
await _mongoContainer.StopAsync();
await _mongoContainer.DisposeAsync();
}

[Fact]
public async Task ArchiveAsync_ExistingUnarchivedIssue_ReturnsTrue()
{
// Arrange
var issue = Issue.Create("Test Issue", "Test Description");
await _repository.CreateAsync(issue);

// Act
var result = await _repository.ArchiveAsync(issue.Id, "testuser");

// Assert
result.Should().BeTrue();

var retrieved = await _repository.GetByIdAsync(issue.Id);
retrieved!.IsArchived.Should().BeTrue();
retrieved.ArchivedBy.Should().Be("testuser");
retrieved.ArchivedAt.Should().NotBeNull();
}

[Fact]
public async Task ArchiveAsync_AlreadyArchivedIssue_ReturnsTrueIdempotent()
{
// Arrange
var issue = Issue.Create("Already Archived Issue", "Description");
await _repository.CreateAsync(issue);
await _repository.ArchiveAsync(issue.Id, "firstuser");

// Act - archive again (already archived, ModifiedCount will be 0)
var result = await _repository.ArchiveAsync(issue.Id, "seconduser");

// Assert - should return true (issue was found), not false (issue not found)
result.Should().BeTrue();
Comment on lines +63 to +74
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ArchiveAsync_AlreadyArchivedIssue_ReturnsTrueIdempotent doesn’t currently exercise the “already archived => ModifiedCount == 0” scenario described in the PR. IssueRepository.ArchiveAsync always sets ArchivedBy, ArchivedAt, and UpdatedAt to new values, so a second call will typically still modify the document (meaning the old ModifiedCount > 0 check would also have returned true). To validate the regression this PR claims to fix, either adjust the repository method to be a true no-op when IsArchived is already true (and assert ArchivedBy/ArchivedAt remain unchanged), or update the test/PR description to reflect that re-archiving mutates metadata and therefore isn’t idempotent.

Suggested change
public async Task ArchiveAsync_AlreadyArchivedIssue_ReturnsTrueIdempotent()
{
// Arrange
var issue = Issue.Create("Already Archived Issue", "Description");
await _repository.CreateAsync(issue);
await _repository.ArchiveAsync(issue.Id, "firstuser");
// Act - archive again (already archived, ModifiedCount will be 0)
var result = await _repository.ArchiveAsync(issue.Id, "seconduser");
// Assert - should return true (issue was found), not false (issue not found)
result.Should().BeTrue();
public async Task ArchiveAsync_AlreadyArchivedIssue_RearchivesAndReturnsTrue()
{
// Arrange
var issue = Issue.Create("Already Archived Issue", "Description");
await _repository.CreateAsync(issue);
await _repository.ArchiveAsync(issue.Id, "firstuser");
// Act - archive again (already archived; repository updates metadata)
var result = await _repository.ArchiveAsync(issue.Id, "seconduser");
// Assert - should return true (issue was found) and reflect latest archive metadata
result.Should().BeTrue();
var retrieved = await _repository.GetByIdAsync(issue.Id);
retrieved!.IsArchived.Should().BeTrue();
retrieved.ArchivedBy.Should().Be("seconduser");
retrieved.ArchivedAt.Should().NotBeNull();

Copilot uses AI. Check for mistakes.
}

[Fact]
public async Task ArchiveAsync_NonExistentIssue_ReturnsFalse()
{
// Act
var result = await _repository.ArchiveAsync("non-existent-id", "testuser");

// Assert
result.Should().BeFalse();
}
}
Loading