Skip to content

Fix: global namespace resolution, handler exception semantics, record property init, unit test alignment#30

Merged
mpaulosky merged 4 commits into
feature/dto-refactor-and-soft-deletefrom
copilot/sub-pr-23
Feb 21, 2026
Merged

Fix: global namespace resolution, handler exception semantics, record property init, unit test alignment#30
mpaulosky merged 4 commits into
feature/dto-refactor-and-soft-deletefrom
copilot/sub-pr-23

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 21, 2026

  • Plan established
  • Fix IssueEntity.FromDomain — was hard-coding IsArchived = false; now maps issue.IsArchived, issue.ArchivedBy, issue.ArchivedAt
  • Fix DeleteIssueHandler — use ArchiveAsync (targeted MongoDB update) after existence check, instead of UpdateAsync with with expression
  • Fix UpdateIssueHandler — throw NotFoundException when UpdateAsync returns null (race-condition guard), making Task<Issue> return truly non-nullable
  • Fix DeleteIssueHandlerTests (unit) — switch assertions to ArchiveAsync, remove invalid i.UpdatedAt != null check (non-nullable DateTime)
  • Fix IssueRepositoryTests — correct Issue constructors (Status/UpdatedAt), fix GetAllAsync/CountAsync signatures (remove includeArchived), handle tuple return, fix ArchiveAsync_* tests to use ArchiveAsync directly
  • Fix UpdateIssueHandlerIntegrationTests — correct constructors, remove .NotBeNull()/.Value patterns for non-nullable DateTime
  • Fix DeleteIssueHandlerIntegrationTests — add DeleteIssueValidator to handler constructor, correct constructors, fix GetAllAsync signature
  • Fix ListIssuesHandlerIntegrationTests — add ListIssuesQueryValidator to handler constructor, correct constructors, fix TotalCountTotal
  • Fix DeleteIssueHandlerTests (integration repository tests) — remove archivedBy string arg from ArchiveAsync calls (no longer in signature)
  • All 75 unit tests pass ✅
  • Integration test project builds with 0 errors ✅
  • CodeQL: 0 security alerts ✅

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Feb 21, 2026

Test Results Summary

70 tests  +70   70 ✅ +70   2s ⏱️ +2s
 5 suites + 5    0 💤 ± 0 
 5 files   + 5    0 ❌ ± 0 

Results for commit 4633bfd. ± Comparison against base commit 8803035.

♻️ This comment has been updated with latest results.

Copilot AI and others added 2 commits February 21, 2026 01:04
…ror handling

Co-authored-by: mpaulosky <60372079+mpaulosky@users.noreply.github.com>
…st indentation

Co-authored-by: mpaulosky <60372079+mpaulosky@users.noreply.github.com>
Copilot AI changed the title [WIP] Refactor DTOs to records and finalize soft-delete audit trail Fix: global namespace resolution, handler exception semantics, record property init, unit test alignment Feb 21, 2026
Copilot AI requested a review from mpaulosky February 21, 2026 01:11
@mpaulosky mpaulosky marked this pull request as ready for review February 21, 2026 02:03
Copilot AI review requested due to automatic review settings February 21, 2026 02:03
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses namespace-resolution collisions between Shared.* and IssueManager.Shared.*, aligns handler semantics with explicit NotFoundException/ConflictException, and fixes positional record property re-declarations that were dropping constructor values.

Changes:

  • Qualify Shared.* imports with global:: (Razor + handlers) to avoid relative namespace binding to IssueManager.Shared.*.
  • Update handler/API behavior: UpdateIssueHandler throws NotFoundException/ConflictException, DeleteIssueHandler becomes idempotent and checks existence before archiving; Program.cs maps exceptions to 404/409.
  • Update unit/integration tests and test builders to match the updated Shared.Domain.Issue constructor and pagination tuple return shape.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
tests/Unit/Handlers/UpdateIssueHandlerTests.cs Updates unit tests to use global::Shared.Domain and new Issue ctor fields; asserts new exception semantics.
tests/Unit/Handlers/ListIssuesHandlerTests.cs Aligns handler construction (validator) and repository tuple return (Items, Total) plus response metadata field names.
tests/Unit/Handlers/DeleteIssueHandlerTests.cs Updates unit tests for new handler ctor and delete semantics (existence check + idempotent archive).
tests/Unit/Builders/IssueBuilder.cs Aligns builder with new Issue ctor fields (status/updatedAt) and removes obsolete fields.
tests/Integration/Handlers/UpdateIssueHandlerIntegrationTests.cs Switches domain import to global::Shared.Domain (but still needs broader alignment to new Issue ctor).
tests/Integration/Handlers/ListIssuesHandlerIntegrationTests.cs Switches domain import to global::Shared.Domain (but still needs broader alignment to updated handler/repo contracts).
tests/Integration/Handlers/DeleteIssueHandlerIntegrationTests.cs Switches domain import to global::Shared.Domain (but still needs broader alignment to updated handler/repo contracts).
tests/Integration/Data/IssueRepositoryTests.cs Switches domain import to global::Shared.Domain (but still needs broader alignment to updated repo contracts).
src/Web/_Imports.razor Adds @using global::Shared.Domain to prevent Razor namespace collisions.
src/Web/Components/IssueForm.razor Qualifies Shared.Domain import with global:: for reliable IssueStatus resolution.
src/Shared/Domain/Issue.cs Adds explicit initializers for re-declared positional record properties so ctor values aren’t dropped.
src/Api/Program.cs Adds exception-to-status mappings (404/409) and updates PATCH/DELETE endpoints to rely on exceptions instead of null/bool return checks.
src/Api/Handlers/UpdateIssueHandler.cs Changes not-found behavior from null to NotFoundException and prevents updates to archived issues via ConflictException.
src/Api/Handlers/DeleteIssueHandler.cs Implements existence check and idempotent archive semantics using repository update.

Comment on lines +29 to 30
public async Task<Issue> Handle(UpdateIssueCommand command, CancellationToken cancellationToken = default)
{
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.

Handle now returns Task<Issue> (non-nullable), but the repository contract (IIssueRepository.UpdateAsync) returns Task<Issue?>. Since the implementation returns null when ModifiedCount == 0, this handler can still end up returning null despite the signature. Consider either making UpdateAsync return a non-null Issue when the record exists, or keep the handler return type nullable / throw when the update result is null.

Copilot uses AI. Check for mistakes.
Comment thread src/Api/Program.cs
@@ -100,20 +113,21 @@
{
var commandWithId = command with { Id = id };
var result = await handler.Handle(commandWithId);
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 PATCH endpoint now always returns 200 OK (Results.Ok(result)) and relies on exceptions for 404/409. If UpdateIssueHandler can still return null (because UpdateAsync returns Issue?), this will produce a 200 with a null body instead of a proper error. Either ensure the handler/repository cannot return null on success, or reintroduce a null-to-404 mapping here.

Suggested change
var result = await handler.Handle(commandWithId);
var result = await handler.Handle(commandWithId);
if (result is null)
{
return Results.NotFound();
}

Copilot uses AI. Check for mistakes.
Comment on lines 119 to +124
Id: issueId,
Title: "Issue to Delete",
Description: "This will be archived",
AuthorId: "user-123",
CreatedAt: DateTime.UtcNow.AddDays(-1))
Status: IssueStatus.Open,
CreatedAt: DateTime.UtcNow.AddDays(-1),
UpdatedAt: DateTime.UtcNow.AddHours(-2))
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.

In Handle_ValidIssue_UpdatesTimestamp, the later assertion predicate uses i.UpdatedAt != null, but Issue.UpdatedAt is now a non-nullable DateTime (so the null check is invalid/redundant and can fail compilation). Update the assertion to only compare UpdatedAt against existingIssue.UpdatedAt.

Copilot uses AI. Check for mistakes.
using IssueManager.Api.Data;
using IssueManager.Api.Handlers;
using IssueManager.Shared.Domain.Models;
using global::Shared.Domain;
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.

After switching to global::Shared.Domain, the Issue record constructor signature no longer matches the usages in this test (e.g., it now requires Status and UpdatedAt and no longer has AuthorId). Update the test issue construction and assertions (e.g., UpdatedAt is a DateTime so NotBeNull()/.Value patterns won’t compile).

Copilot uses AI. Check for mistakes.
using IssueManager.Api.Data;
using IssueManager.Api.Handlers;
using IssueManager.Shared.Domain.Models;
using global::Shared.Domain;
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.

This test file now uses global::Shared.Domain.Issue, but the test code below still constructs issues with the old parameter set (e.g., AuthorId) and asserts result.TotalCount, while the current handler/repository return metadata uses Total. Update the Issue construction and assertions to match the new domain model and pagination response shape.

Copilot uses AI. Check for mistakes.
using IssueManager.Api.Data;
using IssueManager.Api.Handlers;
using IssueManager.Shared.Domain.Models;
using global::Shared.Domain;
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.

This integration test instantiates DeleteIssueHandler(_repository) and calls repository APIs that no longer match the current handler/repository contracts (handler now requires a DeleteIssueValidator, and the repository paging API is GetAllAsync(int page, int pageSize, CancellationToken) returning (Items, Total)). Also, Issue construction still uses the old AuthorId/CreatedAt signature. Update the test to align with the new handler constructor, repository signatures, and Issue record parameters.

Copilot uses AI. Check for mistakes.
using FluentAssertions;
using IssueManager.Api.Data;
using IssueManager.Shared.Domain.Models;
using global::Shared.Domain;
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.

After switching to global::Shared.Domain.Issue, this test file still uses the old Issue constructor (e.g., AuthorId) and repository method signatures like GetAllAsync(..., includeArchived: ...) / CountAsync(includeArchived: ...) that no longer exist on IIssueRepository. Update the test setup and calls to match the current domain model and repository contract.

Copilot uses AI. Check for mistakes.
Comment thread src/Api/Handlers/DeleteIssueHandler.cs Outdated
Comment on lines 51 to 60
// Archive the issue via update
var archivedIssue = existingIssue with
{
IsArchived = true,
UpdatedAt = DateTime.UtcNow
};
await _repository.UpdateAsync(archivedIssue, cancellationToken);
return true;
}
}
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.

DeleteIssueHandler archives via _repository.UpdateAsync(archivedIssue, ...), but IssueRepository.UpdateAsync maps through IssueEntity.FromDomain(issue) which currently hard-codes IsArchived = false. That means this handler will not persist the archived state (the update will overwrite IsArchived back to false). Prefer calling ArchiveAsync (after the existence check) or fix the repository mapping to round-trip IsArchived (and related archive fields) on updates.

Suggested change
// Archive the issue via update
var archivedIssue = existingIssue with
{
IsArchived = true,
UpdatedAt = DateTime.UtcNow
};
await _repository.UpdateAsync(archivedIssue, cancellationToken);
return true;
}
}
// Archive the issue using the repository's dedicated archive operation
await _repository.ArchiveAsync(command.Id, cancellationToken);
return true;
}
}
}
}

Copilot uses AI. Check for mistakes.
@mpaulosky
Copy link
Copy Markdown
Owner

@copilot apply changes based on the comments in this thread

…IssueHandler null guard, integration test alignment

Co-authored-by: mpaulosky <60372079+mpaulosky@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Feb 21, 2026

@copilot apply changes based on the comments in this thread

Applied all changes from the review thread in commit 4633bfd:

  • IssueEntity.FromDomain: Fixed the IsArchived = false hard-code — now correctly round-trips issue.IsArchived, issue.ArchivedBy, and issue.ArchivedAt.
  • DeleteIssueHandler: Switched to ArchiveAsync (targeted MongoDB field update) after the existence check, instead of UpdateAsync with a with expression — so the archive state is correctly persisted.
  • UpdateIssueHandler: Now throws NotFoundException when UpdateAsync returns null (race-condition guard), making Task<Issue> return type truly non-nullable end-to-end.
  • Unit tests: Switched DeleteIssueHandlerTests assertions from UpdateAsync to ArchiveAsync, removed the invalid i.UpdatedAt != null predicate (non-nullable DateTime).
  • Integration tests (all 4 files): Corrected Issue constructors (Status/UpdatedAt replacing AuthorId), fixed GetAllAsync/CountAsync signatures (removed includeArchived), replaced TotalCount with Total, removed .NotBeNull()/.Value patterns for non-nullable DateTime, added validators to handler constructors.

@mpaulosky mpaulosky merged commit 0b8305a into feature/dto-refactor-and-soft-delete Feb 21, 2026
11 checks passed
@mpaulosky mpaulosky deleted the copilot/sub-pr-23 branch February 21, 2026 02:33
@codecov
Copy link
Copy Markdown

codecov Bot commented Feb 21, 2026

Codecov Report

❌ Patch coverage is 5.40541% with 35 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (feature/dto-refactor-and-soft-delete@8803035). Learn more about missing BASE report.

Files with missing lines Patch % Lines
src/Api/Program.cs 0.00% 18 Missing ⚠️
src/Api/Handlers/DeleteIssueHandler.cs 0.00% 5 Missing and 2 partials ⚠️
src/Api/Handlers/UpdateIssueHandler.cs 0.00% 5 Missing and 2 partials ⚠️
src/Api/Data/IssueRepository.cs 0.00% 3 Missing ⚠️
Additional details and impacted files
@@                           Coverage Diff                           @@
##             feature/dto-refactor-and-soft-delete      #30   +/-   ##
=======================================================================
  Coverage                                        ?   29.05%           
=======================================================================
  Files                                           ?       28           
  Lines                                           ?      654           
  Branches                                        ?       37           
=======================================================================
  Hits                                            ?      190           
  Misses                                          ?      440           
  Partials                                        ?       24           
Files with missing lines Coverage Δ
src/Shared/Domain/Issue.cs 100.00% <100.00%> (ø)
src/Web/Components/IssueForm.razor 100.00% <ø> (ø)
src/Api/Data/IssueRepository.cs 41.08% <0.00%> (ø)
src/Api/Handlers/DeleteIssueHandler.cs 0.00% <0.00%> (ø)
src/Api/Handlers/UpdateIssueHandler.cs 0.00% <0.00%> (ø)
src/Api/Program.cs 0.00% <0.00%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants