From 8d496be03c46ea36e9cecf6c290e2ffa08603f10 Mon Sep 17 00:00:00 2001 From: Neil Date: Wed, 21 Oct 2026 07:27:45 -0500 Subject: [PATCH 1/2] feat(tests): backend implementation for replace-fluentassertions-with-xunit --- .claude/agents/backend-builder.md | 6 +- .claude/agents/test-verifier.md | 2 +- README.md | 2 +- .../V1/JournalEntriesControllerV1Tests.cs | 194 +++++++------ .../V1/JournalLogEntriesControllerV1Tests.cs | 68 ++--- .../V1/JournalNotesControllerV1Tests.cs | 50 ++-- .../V1/JournalTodosControllerV1Tests.cs | 93 +++---- .../Controllers/V1/NotesControllerV1Tests.cs | 53 ++-- .../V1/ProjectsControllerV1Tests.cs | 70 ++--- .../V1/TaskItemsControllerV1Tests.cs | 261 +++++++++--------- .../ApiVersioningServiceExtensionsTests.cs | 9 +- .../Extensions/CorsServiceExtensionsTests.cs | 13 +- .../HealthCheckServiceExtensionsTests.cs | 21 +- .../OpenApiServiceExtensionsTests.cs | 13 +- .../OpenTelemetryServiceExtensionsTests.cs | 35 ++- .../PersistenceServiceExtensionsTests.cs | 13 +- .../ServiceCollectionExtensionsTests.cs | 43 ++- .../UtcDateTimeModelBuilderExtensionsTests.cs | 77 +++--- .../HealthChecks/HealthCheckLoggingTests.cs | 47 ++-- .../HealthCheckResponseWriterTests.cs | 32 +-- .../Helpers/DaysTaggedHelperTests.cs | 11 +- .../Helpers/UserDateHelperTests.cs | 15 +- .../JsonSerializerOptionsProviderTests.cs | 15 +- TaskFlow.Api.Tests/README.md | 6 +- .../JournalEntryRepositoryTests.cs | 219 ++++++++------- .../JournalEntryRepositoryUpdateTests.cs | 40 +-- .../JournalLogEntryRepositoryTests.cs | 25 +- .../JournalNoteRepositoryTests.cs | 37 ++- .../Repositories/NoteRepositoryTests.cs | 41 ++- .../Repositories/ProjectRepositoryTests.cs | 59 ++-- .../Repositories/TaskRepositoryTests.cs | 88 +++--- TaskFlow.Api.Tests/TaskFlow.Api.Tests.csproj | 1 - .../AddJournalTodoDtoValidatorTests.cs | 15 +- .../CarryForwardRequestDtoValidatorTests.cs | 5 +- .../CreateJournalEntryDtoValidatorTests.cs | 5 +- .../Validators/JournalEntryValidatorTests.cs | 11 +- .../JournalLogEntryValidatorTests.cs | 19 +- .../Validators/JournalNoteValidatorTests.cs | 19 +- .../Validators/NoteValidatorTests.cs | 17 +- .../Validators/ProjectValidatorTests.cs | 15 +- .../Validators/TaskItemValidatorTests.cs | 57 ++-- docs/CONTRIBUTING.md | 2 +- docs/SERVICE_REGISTRATION_PATTERN.md | 6 +- 43 files changed, 907 insertions(+), 923 deletions(-) diff --git a/.claude/agents/backend-builder.md b/.claude/agents/backend-builder.md index 5f8aa7f4..05954e09 100644 --- a/.claude/agents/backend-builder.md +++ b/.claude/agents/backend-builder.md @@ -7,13 +7,13 @@ color: green --- You are a senior .NET backend engineer with deep expertise in the modern .NET 10 ecosystem. You produce production-quality backend features in layered ASP.NET Core APIs. You work exclusively on backend code and never touch frontend files. -Your expertise spans: .NET 10, C# 13, ASP.NET Core minimal APIs and controller-based APIs, EF Core 9, FluentValidation 11, OpenTelemetry, xUnit/Moq/FluentAssertions, Scalar/OpenAPI, and health check infrastructure. You apply idiomatic modern C# — primary constructors, collection expressions, pattern matching, required members, and `async`/`await` throughout. +Your expertise spans: .NET 10, C# 13, ASP.NET Core minimal APIs and controller-based APIs, EF Core 9, FluentValidation 11, OpenTelemetry, xUnit/Moq, Scalar/OpenAPI, and health check infrastructure. You apply idiomatic modern C# — primary constructors, collection expressions, pattern matching, required members, and `async`/`await` throughout. ## Project Context You are working in the TaskFlow repository, a .NET 10 API with the following stack and conventions: -**Stack:** .NET 10, EF Core + SQLite, FluentValidation, OpenTelemetry → Seq, xUnit/Moq/FluentAssertions, Scalar/OpenAPI +**Stack:** .NET 10, EF Core + SQLite, FluentValidation, OpenTelemetry → Seq, xUnit/Moq, Scalar/OpenAPI **Layered flow:** `Controller → Repository → EF Core (SQLite)` @@ -156,7 +156,7 @@ Notes: **Tests:** - Repository tests: use `DbContextOptions` with `UseInMemoryDatabase`; use real SQLite for constraint tests - Controller tests: use Moq to mock repository interfaces; assert HTTP status codes and response bodies -- Use FluentAssertions (`result.Should().Be(...)`, `result.Should().BeEquivalentTo(...)`, etc.) +- Use xUnit built-in assertions (`Assert.Equal(expected, actual)`, `Assert.IsType(...)`, `Assert.Empty(...)`, `Assert.Throws(...)`, etc.); do not add Fluent-style assertion libraries - Aim for full branch coverage of new code; CI requires 75% line coverage overall - Test naming: `MethodName_Scenario_ExpectedBehavior` diff --git a/.claude/agents/test-verifier.md b/.claude/agents/test-verifier.md index 23bf6f95..372942d7 100644 --- a/.claude/agents/test-verifier.md +++ b/.claude/agents/test-verifier.md @@ -37,7 +37,7 @@ If any of 1–4 are missing, ask for them before proceeding. ## Test Conventions ### Backend (xUnit) -- xUnit (`[Fact]`, `[Theory]`, `[InlineData]`), Moq, FluentAssertions. +- xUnit (`[Fact]`, `[Theory]`, `[InlineData]`), Moq, with xUnit `Assert.*` for assertions. - Tests mirror the main project: `Controllers/V1/`, `Repositories/`, `Validators/`, `HealthChecks/`, `Extensions/` under `TaskFlow.Api.Tests/`. Extend an existing test file for the same class instead of creating a parallel one. - Repository tests use `Microsoft.EntityFrameworkCore.InMemory` by default; use real in-memory SQLite where constraint enforcement matters (e.g., unique index tests). - Controller tests mock repositories with Moq and assert status codes and response bodies. diff --git a/README.md b/README.md index 15c1a104..0cee0225 100644 --- a/README.md +++ b/README.md @@ -152,7 +152,7 @@ TaskFlow/ | Validation | FluentValidation | | Observability | OpenTelemetry (traces, metrics, logs → Seq) | | API docs | OpenAPI + Scalar UI | -| Testing | xUnit, Moq, FluentAssertions, Coverlet | +| Testing | xUnit, Moq, Coverlet | | Deployment | Docker, Portainer GitOps | | CI/CD | GitHub Actions | diff --git a/TaskFlow.Api.Tests/Controllers/V1/JournalEntriesControllerV1Tests.cs b/TaskFlow.Api.Tests/Controllers/V1/JournalEntriesControllerV1Tests.cs index 660a48f7..06ff51e9 100644 --- a/TaskFlow.Api.Tests/Controllers/V1/JournalEntriesControllerV1Tests.cs +++ b/TaskFlow.Api.Tests/Controllers/V1/JournalEntriesControllerV1Tests.cs @@ -1,4 +1,3 @@ -using FluentAssertions; using FluentValidation; using FluentValidation.Results; using Microsoft.AspNetCore.Mvc; @@ -47,7 +46,7 @@ public async Task Create_ShouldNotCarryForward_WhenNotUserTodayOrNoOffset(int da var result = await _controller.Create(new CreateJournalEntryDto { Title = "t", Date = date, TimezoneOffsetMinutes = offset }); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); _repo.Verify(r => r.CarryForwardIncompleteTasksAsync(It.IsAny(), It.IsAny()), Times.Never); } @@ -58,7 +57,7 @@ public async Task Create_ShouldReturnBadRequest_WhenOffsetOutOfRange(int offset) { var result = await _controller.Create(new CreateJournalEntryDto { Title = "t", Date = new DateOnly(2026, 5, 11), TimezoneOffsetMinutes = offset }); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); _repo.Verify(r => r.AddAsync(It.IsAny()), Times.Never); } @@ -72,7 +71,7 @@ public async Task Create_ShouldAcceptBoundaryOffsets(int offset) var result = await _controller.Create(new CreateJournalEntryDto { Title = "t", Date = date, TimezoneOffsetMinutes = offset }); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); } [Theory] @@ -93,11 +92,11 @@ public async Task Create_ShouldCarryForwardAndReload_WhenDateIsUserToday(int off var result = await _controller.Create(new CreateJournalEntryDto { Title = "t", Date = today, TimezoneOffsetMinutes = offset }); - var created = result.Result.Should().BeOfType().Subject; - var body = created.Value.Should().BeOfType().Subject; - body.TodoTaskItemIds.Should().BeEquivalentTo([5, 6]); - body.MovedCount.Should().Be(2); - body.MovedTaskItemIds.Should().Equal(3, 5); + var created = Assert.IsType(result.Result); + var body = Assert.IsType(created.Value); + Assert.Equal(new[] { 5, 6 }, body.TodoTaskItemIds.Order()); + Assert.Equal(2, body.MovedCount); + Assert.Equal(new[] { 3, 5 }, body.MovedTaskItemIds); _repo.Verify(r => r.CarryForwardIncompleteTasksAsync(21, today), Times.Once); } @@ -116,11 +115,11 @@ public async Task Create_ShouldReturnPersistedTodos_WhenCarryForwardMovedNothing var result = await _controller.Create(new CreateJournalEntryDto { Title = "t", Date = today, TimezoneOffsetMinutes = offset }); - var created = result.Result.Should().BeOfType().Subject; - var body = created.Value.Should().BeOfType().Subject; - body.TodoTaskItemIds.Should().BeEmpty(); - body.MovedCount.Should().Be(0); - body.MovedTaskItemIds.Should().BeEmpty(); + var created = Assert.IsType(result.Result); + var body = Assert.IsType(created.Value); + Assert.Empty(body.TodoTaskItemIds); + Assert.Equal(0, body.MovedCount); + Assert.Empty(body.MovedTaskItemIds); _repo.Verify(r => r.CarryForwardIncompleteTasksAsync(22, today), Times.Once); } @@ -135,10 +134,9 @@ public async Task Create_ShouldReturnZeroAndEmptyIds_WhenCarryForwardNotAttempte var result = await _controller.Create(new CreateJournalEntryDto { Title = "t", Date = date, TimezoneOffsetMinutes = offset }); - var body = result.Result.Should().BeOfType().Subject.Value - .Should().BeOfType().Subject; - body.MovedCount.Should().Be(0); - body.MovedTaskItemIds.Should().BeEmpty(); + var body = Assert.IsType(Assert.IsType(result.Result).Value); + Assert.Equal(0, body.MovedCount); + Assert.Empty(body.MovedTaskItemIds); _repo.Verify(r => r.CarryForwardIncompleteTasksAsync(It.IsAny(), It.IsAny()), Times.Never); } @@ -150,11 +148,10 @@ public async Task Create_ShouldReturnConflictWithZeroMoved_WhenEntryAlreadyExist var result = await _controller.Create(new CreateJournalEntryDto { Title = "t", Date = date, TimezoneOffsetMinutes = 0 }); - var conflict = result.Result.Should().BeOfType().Subject; - var existing = conflict.Value!.GetType().GetProperty("existingEntry")!.GetValue(conflict.Value) - .Should().BeOfType().Subject; - existing.MovedCount.Should().Be(0); - existing.MovedTaskItemIds.Should().BeEmpty(); + var conflict = Assert.IsType(result.Result); + var existing = Assert.IsType(conflict.Value!.GetType().GetProperty("existingEntry")!.GetValue(conflict.Value)); + Assert.Equal(0, existing.MovedCount); + Assert.Empty(existing.MovedTaskItemIds); _repo.Verify(r => r.CarryForwardIncompleteTasksAsync(It.IsAny(), It.IsAny()), Times.Never); } @@ -170,11 +167,10 @@ public async Task Create_ShouldReturnConflict_WhenDuplicateDateRaceOccurs() var result = await _controller.Create(new CreateJournalEntryDto { Title = "t", Date = date, TimezoneOffsetMinutes = 0 }); - var conflict = result.Result.Should().BeOfType().Subject; - var existing = conflict.Value!.GetType().GetProperty("existingEntry")!.GetValue(conflict.Value) - .Should().BeOfType().Subject; - existing.MovedCount.Should().Be(0); - existing.MovedTaskItemIds.Should().BeEmpty(); + var conflict = Assert.IsType(result.Result); + var existing = Assert.IsType(conflict.Value!.GetType().GetProperty("existingEntry")!.GetValue(conflict.Value)); + Assert.Equal(0, existing.MovedCount); + Assert.Empty(existing.MovedTaskItemIds); _repo.Verify(r => r.CarryForwardIncompleteTasksAsync(It.IsAny(), It.IsAny()), Times.Never); } @@ -189,10 +185,9 @@ public async Task Create_ShouldReturnZeroMoved_WhenCarryForwardLosesRaceAndRetur var result = await _controller.Create(new CreateJournalEntryDto { Title = "t", Date = today, TimezoneOffsetMinutes = offset }); - var body = result.Result.Should().BeOfType().Subject.Value - .Should().BeOfType().Subject; - body.MovedCount.Should().Be(0); - body.MovedTaskItemIds.Should().BeEmpty(); + var body = Assert.IsType(Assert.IsType(result.Result).Value); + Assert.Equal(0, body.MovedCount); + Assert.Empty(body.MovedTaskItemIds); _repo.Verify(r => r.CarryForwardIncompleteTasksAsync(23, today), Times.Once); } @@ -202,8 +197,10 @@ public void ResponseDtos_ShouldSerializeMovedFields_WhenEmpty() var entryJson = System.Text.Json.JsonSerializer.Serialize(new JournalEntryResponseDto { Title = "t" }); var carryJson = System.Text.Json.JsonSerializer.Serialize(new CarryForwardResponseDto()); - entryJson.Should().Contain("\"MovedCount\":0").And.Contain("\"MovedTaskItemIds\":[]"); - carryJson.Should().Contain("\"MovedCount\":0").And.Contain("\"MovedTaskItemIds\":[]"); + Assert.Contains("\"MovedCount\":0", entryJson); + Assert.Contains("\"MovedTaskItemIds\":[]", entryJson); + Assert.Contains("\"MovedCount\":0", carryJson); + Assert.Contains("\"MovedTaskItemIds\":[]", carryJson); } [Fact] @@ -215,9 +212,9 @@ public async Task CarryForward_ShouldReturnZeroAndEmpty_WhenInlineCarryForwardAl var result = await _controller.CarryForward(3, new CarryForwardRequestDto { TimezoneOffsetMinutes = 0 }); - var body = result.Result.Should().BeOfType().Subject.Value.Should().BeOfType().Subject; - body.MovedCount.Should().Be(0); - body.MovedTaskItemIds.Should().BeEmpty(); + var body = Assert.IsType(Assert.IsType(result.Result).Value); + Assert.Equal(0, body.MovedCount); + Assert.Empty(body.MovedTaskItemIds); } [Fact] @@ -225,7 +222,7 @@ public async Task CarryForward_ShouldReturnBadRequest_WhenOffsetOutOfRange() { var result = await _controller.CarryForward(1, new CarryForwardRequestDto { TimezoneOffsetMinutes = 841 }); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); } [Fact] @@ -235,7 +232,7 @@ public async Task CarryForward_ShouldReturnNotFound_WhenEntryMissing() var result = await _controller.CarryForward(5, new CarryForwardRequestDto { TimezoneOffsetMinutes = 0 }); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); } [Theory] @@ -248,9 +245,9 @@ public async Task CarryForward_ShouldReturnZeroWithoutRepoCall_WhenOffsetMissing var result = await _controller.CarryForward(3, nullBody ? null : new CarryForwardRequestDto()); - var body = result.Result.Should().BeOfType().Subject.Value.Should().BeOfType().Subject; - body.MovedCount.Should().Be(0); - body.MovedTaskItemIds.Should().BeEmpty(); + var body = Assert.IsType(Assert.IsType(result.Result).Value); + Assert.Equal(0, body.MovedCount); + Assert.Empty(body.MovedTaskItemIds); _repo.Verify(r => r.CarryForwardIncompleteTasksAsync(It.IsAny(), It.IsAny()), Times.Never); } @@ -262,9 +259,9 @@ public async Task CarryForward_ShouldReturnZeroWithoutRepoCall_WhenEntryIsNotUse var result = await _controller.CarryForward(3, new CarryForwardRequestDto { TimezoneOffsetMinutes = 0 }); - var body = result.Result.Should().BeOfType().Subject.Value.Should().BeOfType().Subject; - body.MovedCount.Should().Be(0); - body.MovedTaskItemIds.Should().BeEmpty(); + var body = Assert.IsType(Assert.IsType(result.Result).Value); + Assert.Equal(0, body.MovedCount); + Assert.Empty(body.MovedTaskItemIds); _repo.Verify(r => r.CarryForwardIncompleteTasksAsync(It.IsAny(), It.IsAny()), Times.Never); } @@ -277,9 +274,9 @@ public async Task CarryForward_ShouldReturnRepoCount_WhenEntryIsUserToday() var result = await _controller.CarryForward(3, new CarryForwardRequestDto { TimezoneOffsetMinutes = 840 }); - var body = result.Result.Should().BeOfType().Subject.Value.Should().BeOfType().Subject; - body.MovedCount.Should().Be(4); - body.MovedTaskItemIds.Should().Equal(7, 8, 9, 12); + var body = Assert.IsType(Assert.IsType(result.Result).Value); + Assert.Equal(4, body.MovedCount); + Assert.Equal(new[] { 7, 8, 9, 12 }, body.MovedTaskItemIds); } [Fact] @@ -293,9 +290,9 @@ public async Task GetAll_ShouldReturnOk() var result = await _controller.GetAll(); - var ok = result.Result.Should().BeOfType().Subject; - var dtos = ok.Value.Should().BeAssignableTo>().Subject; - dtos.Should().ContainSingle(d => d.Id == 1 && d.Date == new DateOnly(2026, 5, 10)); + var ok = Assert.IsType(result.Result); + var dtos = Assert.IsAssignableFrom>(ok.Value); + Assert.Single(dtos, d => d.Id == 1 && d.Date == new DateOnly(2026, 5, 10)); } [Fact] @@ -305,7 +302,7 @@ public async Task Get_ShouldReturnNotFound_WhenMissing() var result = await _controller.Get(123); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); } [Fact] @@ -317,7 +314,7 @@ public async Task Create_ShouldReturnConflict_WhenDateAlreadyExists() var result = await _controller.Create(new CreateJournalEntryDto { Title = "New", Date = date }); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); _repo.Verify(r => r.AddAsync(It.IsAny()), Times.Never); } @@ -331,7 +328,7 @@ public async Task Create_ShouldReturnBadRequest_WhenValidationFails() var result = await _controller.Create(new CreateJournalEntryDto { Title = string.Empty, Date = date }); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); _repo.Verify(r => r.AddAsync(It.IsAny()), Times.Never); } @@ -347,9 +344,10 @@ public async Task Create_ShouldReturnCreated_WhenValid() var result = await _controller.Create(new CreateJournalEntryDto { Title = "May 11", Date = date }); - var created = result.Result.Should().BeOfType().Subject; - created.RouteName.Should().Be("GetJournalEntryV1"); - created.RouteValues.Should().ContainKey("id").WhoseValue.Should().Be(9); + var created = Assert.IsType(result.Result); + Assert.Equal("GetJournalEntryV1", created.RouteName); + Assert.True(created.RouteValues!.TryGetValue("id", out var createdId)); + Assert.Equal(9, createdId); } [Fact] @@ -365,7 +363,7 @@ public async Task Create_ShouldReturnConflict_WhenUniqueRaceOccurs() var result = await _controller.Create(new CreateJournalEntryDto { Title = "May 12", Date = date }); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); } [Fact] @@ -375,7 +373,7 @@ public async Task Update_ShouldReturnNotFound_WhenMissing() var result = await _controller.Update(44, new UpdateJournalEntryDto { Title = "x", Summary = "s" }); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); } [Fact] @@ -385,7 +383,7 @@ public async Task Delete_ShouldReturnNoContent_WhenFound() var result = await _controller.Delete(7); - result.Should().BeOfType(); + Assert.IsType(result); _repo.Verify(r => r.DeleteAsync(7), Times.Once); } @@ -450,13 +448,13 @@ public async Task Create_EndToEnd_ShouldCarryIncompleteTasksUsingClientOffsetNot var result = await h.Controller.Create(new CreateJournalEntryDto { Title = "t", Date = userToday, TimezoneOffsetMinutes = offset }); - var body = result.Result.Should().BeOfType().Subject.Value.Should().BeOfType().Subject; - body.TodoTaskItemIds.Should().BeEquivalentTo([open.Id]); + var body = Assert.IsType(Assert.IsType(result.Result).Value); + Assert.Equal(new[] { open.Id }, body.TodoTaskItemIds.Order()); var reloaded = await h.Context.TaskItems.AsNoTracking().SingleAsync(t => t.Id == open.Id); - reloaded.MoveCount.Should().Be(1); - reloaded.CurrentJournalEntryId.Should().Be(body.Id); - (await h.Context.TaskItems.AsNoTracking().SingleAsync(t => t.Id == done.Id)).CurrentJournalEntryId.Should().Be(old.Id); - (await h.Context.TaskItemEvents.CountAsync()).Should().Be(1); + Assert.Equal(1, reloaded.MoveCount); + Assert.Equal(body.Id, reloaded.CurrentJournalEntryId); + Assert.Equal(old.Id, (await h.Context.TaskItems.AsNoTracking().SingleAsync(t => t.Id == done.Id)).CurrentJournalEntryId); + Assert.Equal(1, (await h.Context.TaskItemEvents.CountAsync())); } [Fact] @@ -469,8 +467,8 @@ public async Task Create_EndToEnd_ShouldNotMoveTasks_WhenDateIsUtcTodayButNotUse var result = await h.Controller.Create(new CreateJournalEntryDto { Title = "t", Date = utcDate, TimezoneOffsetMinutes = offset }); - result.Result.Should().BeOfType(); - (await h.Context.TaskItems.AsNoTracking().SingleAsync(t => t.Id == open.Id)).MoveCount.Should().Be(0); + Assert.IsType(result.Result); + Assert.Equal(0, (await h.Context.TaskItems.AsNoTracking().SingleAsync(t => t.Id == open.Id)).MoveCount); } [Theory] @@ -486,10 +484,10 @@ public async Task Create_EndToEnd_ShouldNotMoveTasks_WhenOffsetMissingOrDateNotT var result = await h.Controller.Create(new CreateJournalEntryDto { Title = "t", Date = utcDate.AddDays(dayDelta), TimezoneOffsetMinutes = offset }); - var body = result.Result.Should().BeOfType().Subject.Value.Should().BeOfType().Subject; - body.TodoTaskItemIds.Should().BeEmpty(); - (await h.Context.TaskItems.AsNoTracking().SingleAsync(t => t.Id == open.Id)).MoveCount.Should().Be(0); - (await h.Context.TaskItemEvents.CountAsync()).Should().Be(0); + var body = Assert.IsType(Assert.IsType(result.Result).Value); + Assert.Empty(body.TodoTaskItemIds); + Assert.Equal(0, (await h.Context.TaskItems.AsNoTracking().SingleAsync(t => t.Id == open.Id)).MoveCount); + Assert.Equal(0, (await h.Context.TaskItemEvents.CountAsync())); } [Fact] @@ -504,12 +502,12 @@ public async Task CarryForward_EndToEnd_ShouldMoveOnceThenReportZero_WhenCalledT var first = await h.Controller.CarryForward(todayEntry.Id, new CarryForwardRequestDto { TimezoneOffsetMinutes = offset }); var second = await h.Controller.CarryForward(todayEntry.Id, new CarryForwardRequestDto { TimezoneOffsetMinutes = offset }); - first.Result.Should().BeOfType().Which.Value.Should().BeOfType().Which.MovedCount.Should().Be(1); - second.Result.Should().BeOfType().Which.Value.Should().BeOfType().Which.MovedCount.Should().Be(0); + Assert.Equal(1, Assert.IsType(Assert.IsType(first.Result).Value).MovedCount); + Assert.Equal(0, Assert.IsType(Assert.IsType(second.Result).Value).MovedCount); var reloaded = await h.Context.TaskItems.AsNoTracking().SingleAsync(t => t.Id == open.Id); - reloaded.MoveCount.Should().Be(1); - reloaded.CurrentJournalEntryId.Should().Be(todayEntry.Id); - (await h.Context.TaskItemEvents.CountAsync()).Should().Be(1); + Assert.Equal(1, reloaded.MoveCount); + Assert.Equal(todayEntry.Id, reloaded.CurrentJournalEntryId); + Assert.Equal(1, (await h.Context.TaskItemEvents.CountAsync())); } [Fact] @@ -522,13 +520,13 @@ public async Task Create_EndToEnd_ShouldReportExactMovedTasks_WhenCreatingUserTo var result = await h.Controller.Create(new CreateJournalEntryDto { Title = "t", Date = userToday, TimezoneOffsetMinutes = offset }); - var body = result.Result.Should().BeOfType().Subject.Value.Should().BeOfType().Subject; - body.MovedCount.Should().Be(1); - body.MovedTaskItemIds.Should().Equal(open.Id); - body.MovedTaskItemIds.Should().NotContain(done.Id); - body.TodoTaskItemIds.Should().Contain(body.MovedTaskItemIds); + var body = Assert.IsType(Assert.IsType(result.Result).Value); + Assert.Equal(1, body.MovedCount); + Assert.Equal(new[] { open.Id }, body.MovedTaskItemIds); + Assert.DoesNotContain(done.Id, body.MovedTaskItemIds); + Assert.All(body.MovedTaskItemIds, movedId => Assert.Contains(movedId, body.TodoTaskItemIds)); var history = await h.Context.TaskItemEvents.AsNoTracking().SingleAsync(e => e.TaskItemId == open.Id); - history.ChangeSummary.Should().Be("Automatically moved forward to the next journal day."); + Assert.Equal("Automatically moved forward to the next journal day.", history.ChangeSummary); } [Theory] @@ -544,9 +542,9 @@ public async Task Create_EndToEnd_ShouldReportZeroMovedAndEmptyIds_WhenOffsetMis var result = await h.Controller.Create(new CreateJournalEntryDto { Title = "t", Date = utcDate.AddDays(dayDelta), TimezoneOffsetMinutes = offset }); - var body = result.Result.Should().BeOfType().Subject.Value.Should().BeOfType().Subject; - body.MovedCount.Should().Be(0); - body.MovedTaskItemIds.Should().BeEmpty(); + var body = Assert.IsType(Assert.IsType(result.Result).Value); + Assert.Equal(0, body.MovedCount); + Assert.Empty(body.MovedTaskItemIds); } [Fact] @@ -558,9 +556,9 @@ public async Task Create_EndToEnd_ShouldReportZeroMovedAndEmptyIds_WhenNoIncompl var result = await h.Controller.Create(new CreateJournalEntryDto { Title = "t", Date = userToday, TimezoneOffsetMinutes = offset }); - var body = result.Result.Should().BeOfType().Subject.Value.Should().BeOfType().Subject; - body.MovedCount.Should().Be(0); - body.MovedTaskItemIds.Should().BeEmpty(); + var body = Assert.IsType(Assert.IsType(result.Result).Value); + Assert.Equal(0, body.MovedCount); + Assert.Empty(body.MovedTaskItemIds); } [Fact] @@ -575,12 +573,12 @@ public async Task CarryForward_EndToEnd_ShouldReturnMovedIdsThenEmpty_WhenCalled var first = await h.Controller.CarryForward(todayEntry.Id, new CarryForwardRequestDto { TimezoneOffsetMinutes = offset }); var second = await h.Controller.CarryForward(todayEntry.Id, new CarryForwardRequestDto { TimezoneOffsetMinutes = offset }); - var firstBody = first.Result.Should().BeOfType().Subject.Value.Should().BeOfType().Subject; - firstBody.MovedCount.Should().Be(1); - firstBody.MovedTaskItemIds.Should().Equal(open.Id); - var secondBody = second.Result.Should().BeOfType().Subject.Value.Should().BeOfType().Subject; - secondBody.MovedCount.Should().Be(0); - secondBody.MovedTaskItemIds.Should().BeEmpty(); + var firstBody = Assert.IsType(Assert.IsType(first.Result).Value); + Assert.Equal(1, firstBody.MovedCount); + Assert.Equal(new[] { open.Id }, firstBody.MovedTaskItemIds); + var secondBody = Assert.IsType(Assert.IsType(second.Result).Value); + Assert.Equal(0, secondBody.MovedCount); + Assert.Empty(secondBody.MovedTaskItemIds); } [Fact] @@ -593,7 +591,7 @@ public async Task CarryForward_EndToEnd_ShouldMoveNothing_WhenNoOffsetOrEntryNot // userToday is utcDate +/- 1 (never equal), so offsets of -2, +5 from userToday cannot hit userToday or utcDate at any hour. var oldDate = userToday.AddDays(-2); var futureDate = userToday.AddDays(5); - new[] { oldDate, userToday, futureDate, utcDate }.Distinct().Should().HaveCount(4); + Assert.Equal(4, new[] { oldDate, userToday, futureDate, utcDate }.Distinct().Count()); var (_, open, _) = await h.SeedOldEntryAsync(oldDate); var todayEntry = await h.Repo.AddAsync(new JournalEntry { Title = "Today", Date = userToday }); var tomorrowEntry = await h.Repo.AddAsync(new JournalEntry { Title = "Future", Date = futureDate }); @@ -605,9 +603,9 @@ public async Task CarryForward_EndToEnd_ShouldMoveNothing_WhenNoOffsetOrEntryNot foreach (var r in new[] { noOffset, tomorrow, utcOnly }) { - r.Result.Should().BeOfType().Which.Value.Should().BeOfType().Which.MovedCount.Should().Be(0); + Assert.Equal(0, Assert.IsType(Assert.IsType(r.Result).Value).MovedCount); } - (await h.Context.TaskItems.AsNoTracking().SingleAsync(t => t.Id == open.Id)).MoveCount.Should().Be(0); + Assert.Equal(0, (await h.Context.TaskItems.AsNoTracking().SingleAsync(t => t.Id == open.Id)).MoveCount); } } diff --git a/TaskFlow.Api.Tests/Controllers/V1/JournalLogEntriesControllerV1Tests.cs b/TaskFlow.Api.Tests/Controllers/V1/JournalLogEntriesControllerV1Tests.cs index 2733586d..4265fb21 100644 --- a/TaskFlow.Api.Tests/Controllers/V1/JournalLogEntriesControllerV1Tests.cs +++ b/TaskFlow.Api.Tests/Controllers/V1/JournalLogEntriesControllerV1Tests.cs @@ -1,4 +1,3 @@ -using FluentAssertions; using FluentValidation; using FluentValidation.Results; using Microsoft.AspNetCore.Mvc; @@ -45,7 +44,7 @@ public async Task GetAll_ShouldReturnNotFound_WhenEntryMissing() var result = await _controller.GetAll(99); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); } [Fact] @@ -57,10 +56,10 @@ public async Task Get_ShouldReturnOk_WhenFound() var result = await _controller.Get(1, 2); - var ok = result.Result.Should().BeOfType().Subject; - var dto = ok.Value.Should().BeOfType().Subject; - dto.Id.Should().Be(2); - dto.JournalEntryId.Should().Be(1); + var ok = Assert.IsType(result.Result); + var dto = Assert.IsType(ok.Value); + Assert.Equal(2, dto.Id); + Assert.Equal(1, dto.JournalEntryId); } // ── CREATE ──────────────────────────────────────────────────────────────── @@ -74,7 +73,7 @@ public async Task Create_ShouldReturnBadRequest_WhenValidationFails() var result = await _controller.Create(1, new CreateJournalLogEntryDto { Content = string.Empty }); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); _logRepo.Verify(s => s.AddAsync(It.IsAny()), Times.Never); } @@ -88,9 +87,10 @@ public async Task Create_ShouldReturnCreated_WhenValid() var result = await _controller.Create(1, new CreateJournalLogEntryDto { Content = "Done" }); - var created = result.Result.Should().BeOfType().Subject; - created.RouteName.Should().Be("GetJournalLogEntryV1"); - created.RouteValues.Should().ContainKey("id").WhoseValue.Should().Be(3); + var created = Assert.IsType(result.Result); + Assert.Equal("GetJournalLogEntryV1", created.RouteName); + Assert.True(created.RouteValues!.TryGetValue("id", out var createdId)); + Assert.Equal(3, createdId); } [Fact] @@ -103,10 +103,10 @@ public async Task Create_ShouldSucceed_WhenTaskItemIdOmitted() var result = await _controller.Create(1, new CreateJournalLogEntryDto { Content = "Note" }); - var created = result.Result.Should().BeOfType().Subject; - var dto = created.Value.Should().BeOfType().Subject; - dto.TaskItemId.Should().BeNull(); - dto.LinkedTaskDeleted.Should().BeFalse(); + var created = Assert.IsType(result.Result); + var dto = Assert.IsType(created.Value); + Assert.Null(dto.TaskItemId); + Assert.False(dto.LinkedTaskDeleted); _taskRepo.Verify(r => r.GetByIdAsync(It.IsAny()), Times.Never); } @@ -128,11 +128,11 @@ public async Task Create_ShouldLinkTask_WhenTaskItemIdValid() var result = await _controller.Create(1, new CreateJournalLogEntryDto { Content = "Did work", TaskItemId = 10 }); - var created = result.Result.Should().BeOfType().Subject; - var dto = created.Value.Should().BeOfType().Subject; - dto.TaskItemId.Should().Be(10); - dto.LinkedTaskTitle.Should().Be("Build feature"); - dto.LinkedTaskDeleted.Should().BeFalse(); + var created = Assert.IsType(result.Result); + var dto = Assert.IsType(created.Value); + Assert.Equal(10, dto.TaskItemId); + Assert.Equal("Build feature", dto.LinkedTaskTitle); + Assert.False(dto.LinkedTaskDeleted); } [Fact] @@ -144,7 +144,7 @@ public async Task Create_ShouldReturnBadRequest_WhenTaskItemIdNotFound() var result = await _controller.Create(1, new CreateJournalLogEntryDto { Content = "Work", TaskItemId = 99 }); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); _logRepo.Verify(s => s.AddAsync(It.IsAny()), Times.Never); } @@ -157,7 +157,7 @@ public async Task Update_ShouldReturnNotFound_WhenEntryMissing() var result = await _controller.Update(99, 1, new UpdateJournalLogEntryDto { Content = "x" }); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); } [Fact] @@ -168,7 +168,7 @@ public async Task Update_ShouldReturnNotFound_WhenLogMissing() var result = await _controller.Update(1, 22, new UpdateJournalLogEntryDto { Content = "x" }); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); } [Fact] @@ -182,7 +182,7 @@ public async Task Update_ShouldReturnBadRequest_WhenTaskItemIdNotFound() var result = await _controller.Update(1, 3, new UpdateJournalLogEntryDto { Content = "New", TaskItemId = 42 }); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); _logRepo.Verify(r => r.UpdateAsync(It.IsAny()), Times.Never); } @@ -198,11 +198,11 @@ public async Task Update_ShouldChangeLink_WhenTaskItemIdValid() var result = await _controller.Update(1, 3, new UpdateJournalLogEntryDto { Content = "Updated", TaskItemId = 20 }); - var ok = result.Result.Should().BeOfType().Subject; - var dto = ok.Value.Should().BeOfType().Subject; - dto.TaskItemId.Should().Be(20); - dto.LinkedTaskTitle.Should().Be("New Task"); - dto.LinkedTaskDeleted.Should().BeFalse(); + var ok = Assert.IsType(result.Result); + var dto = Assert.IsType(ok.Value); + Assert.Equal(20, dto.TaskItemId); + Assert.Equal("New Task", dto.LinkedTaskTitle); + Assert.False(dto.LinkedTaskDeleted); _logRepo.Verify(r => r.UpdateAsync(existing), Times.Once); } @@ -223,11 +223,11 @@ public async Task Update_ShouldClearLink_WhenTaskItemIdIsNull() var result = await _controller.Update(1, 3, new UpdateJournalLogEntryDto { Content = "Updated" }); - var ok = result.Result.Should().BeOfType().Subject; - var dto = ok.Value.Should().BeOfType().Subject; - dto.TaskItemId.Should().BeNull(); - dto.LinkedTaskTitle.Should().BeNull(); - dto.LinkedTaskDeleted.Should().BeFalse(); + var ok = Assert.IsType(result.Result); + var dto = Assert.IsType(ok.Value); + Assert.Null(dto.TaskItemId); + Assert.Null(dto.LinkedTaskTitle); + Assert.False(dto.LinkedTaskDeleted); _taskRepo.Verify(r => r.GetByIdAsync(It.IsAny()), Times.Never); } @@ -242,7 +242,7 @@ public async Task Delete_ShouldReturnNoContent_WhenFound() var result = await _controller.Delete(1, 4); - result.Should().BeOfType(); + Assert.IsType(result); _logRepo.Verify(s => s.DeleteAsync(1, 4), Times.Once); } } diff --git a/TaskFlow.Api.Tests/Controllers/V1/JournalNotesControllerV1Tests.cs b/TaskFlow.Api.Tests/Controllers/V1/JournalNotesControllerV1Tests.cs index b2242390..0e316ea4 100644 --- a/TaskFlow.Api.Tests/Controllers/V1/JournalNotesControllerV1Tests.cs +++ b/TaskFlow.Api.Tests/Controllers/V1/JournalNotesControllerV1Tests.cs @@ -1,4 +1,3 @@ -using FluentAssertions; using FluentValidation; using FluentValidation.Results; using Microsoft.AspNetCore.Mvc; @@ -39,9 +38,9 @@ public async Task GetAll_ShouldReturn200_WithNotes_WhenEntryExists() var result = await _controller.GetAll(1); - var ok = result.Result.Should().BeOfType().Subject; - var dtos = ok.Value.Should().BeAssignableTo>().Subject; - dtos.Should().HaveCount(2); + var ok = Assert.IsType(result.Result); + var dtos = Assert.IsAssignableFrom>(ok.Value); + Assert.Equal(2, dtos.Count()); } [Fact] @@ -51,7 +50,7 @@ public async Task GetAll_ShouldReturn404_WhenEntryMissing() var result = await _controller.GetAll(99); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); } // ── GetById ───────────────────────────────────────────────────────────── @@ -64,10 +63,10 @@ public async Task GetById_ShouldReturn200_WhenFound() var result = await _controller.Get(1, 5); - var ok = result.Result.Should().BeOfType().Subject; - var dto = ok.Value.Should().BeOfType().Subject; - dto.Id.Should().Be(5); - dto.JournalEntryId.Should().Be(1); + var ok = Assert.IsType(result.Result); + var dto = Assert.IsType(ok.Value); + Assert.Equal(5, dto.Id); + Assert.Equal(1, dto.JournalEntryId); } [Fact] @@ -78,7 +77,7 @@ public async Task GetById_ShouldReturn404_WhenNoteMissing() var result = await _controller.Get(1, 99); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); } [Fact] @@ -88,7 +87,7 @@ public async Task GetById_ShouldReturn404_WhenEntryMissing() var result = await _controller.Get(99, 1); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); } // ── Create ────────────────────────────────────────────────────────────── @@ -104,9 +103,10 @@ public async Task Create_ShouldReturn201_WhenValid() var result = await _controller.Create(1, new CreateJournalNoteDto { Content = "A note" }); - var created = result.Result.Should().BeOfType().Subject; - created.RouteName.Should().Be("GetJournalNoteV1"); - created.RouteValues.Should().ContainKey("id").WhoseValue.Should().Be(7); + var created = Assert.IsType(result.Result); + Assert.Equal("GetJournalNoteV1", created.RouteName); + Assert.True(created.RouteValues!.TryGetValue("id", out var createdId)); + Assert.Equal(7, createdId); } [Fact] @@ -116,7 +116,7 @@ public async Task Create_ShouldReturn404_WhenEntryMissing() var result = await _controller.Create(99, new CreateJournalNoteDto { Content = "x" }); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); _noteRepo.Verify(s => s.AddAsync(It.IsAny()), Times.Never); } @@ -129,7 +129,7 @@ public async Task Create_ShouldReturn400_WhenContentIsEmpty() var result = await _controller.Create(1, new CreateJournalNoteDto { Content = string.Empty }); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); _noteRepo.Verify(s => s.AddAsync(It.IsAny()), Times.Never); } @@ -145,9 +145,9 @@ public async Task Update_ShouldReturn200_WhenValid() var result = await _controller.Update(1, 3, new UpdateJournalNoteDto { Content = "Updated" }); - var ok = result.Result.Should().BeOfType().Subject; - var dto = ok.Value.Should().BeOfType().Subject; - dto.Content.Should().Be("Updated"); + var ok = Assert.IsType(result.Result); + var dto = Assert.IsType(ok.Value); + Assert.Equal("Updated", dto.Content); _noteRepo.Verify(s => s.UpdateAsync(It.IsAny()), Times.Once); } @@ -158,7 +158,7 @@ public async Task Update_ShouldReturn404_WhenEntryMissing() var result = await _controller.Update(99, 1, new UpdateJournalNoteDto { Content = "x" }); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); } [Fact] @@ -169,7 +169,7 @@ public async Task Update_ShouldReturn404_WhenNoteMissing() var result = await _controller.Update(1, 99, new UpdateJournalNoteDto { Content = "x" }); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); } [Fact] @@ -182,7 +182,7 @@ public async Task Update_ShouldReturn400_WhenContentIsEmpty() var result = await _controller.Update(1, 3, new UpdateJournalNoteDto { Content = string.Empty }); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); _noteRepo.Verify(s => s.UpdateAsync(It.IsAny()), Times.Never); } @@ -196,7 +196,7 @@ public async Task Delete_ShouldReturn204_WhenFound() var result = await _controller.Delete(1, 4); - result.Should().BeOfType(); + Assert.IsType(result); _noteRepo.Verify(s => s.DeleteAsync(1, 4), Times.Once); } @@ -207,7 +207,7 @@ public async Task Delete_ShouldReturn404_WhenEntryMissing() var result = await _controller.Delete(99, 1); - result.Should().BeOfType(); + Assert.IsType(result); _noteRepo.Verify(s => s.DeleteAsync(It.IsAny(), It.IsAny()), Times.Never); } @@ -219,7 +219,7 @@ public async Task Delete_ShouldReturn404_WhenNoteMissing() var result = await _controller.Delete(1, 99); - result.Should().BeOfType(); + Assert.IsType(result); _noteRepo.Verify(s => s.DeleteAsync(It.IsAny(), It.IsAny()), Times.Never); } } diff --git a/TaskFlow.Api.Tests/Controllers/V1/JournalTodosControllerV1Tests.cs b/TaskFlow.Api.Tests/Controllers/V1/JournalTodosControllerV1Tests.cs index 8f59a77c..c9bcbfca 100644 --- a/TaskFlow.Api.Tests/Controllers/V1/JournalTodosControllerV1Tests.cs +++ b/TaskFlow.Api.Tests/Controllers/V1/JournalTodosControllerV1Tests.cs @@ -1,4 +1,3 @@ -using FluentAssertions; using FluentValidation; using FluentValidation.Results; using Microsoft.AspNetCore.Mvc; @@ -31,7 +30,7 @@ public async Task GetAll_ShouldReturnNotFound_WhenEntryMissing() var result = await _controller.GetAll(1); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); } [Fact] @@ -45,9 +44,9 @@ public async Task GetAll_ShouldReturnOk_WhenEntryExists() var result = await _controller.GetAll(1); - var ok = result.Result.Should().BeOfType().Subject; - var todos = ok.Value.Should().BeAssignableTo>().Subject; - todos.Should().ContainSingle(t => t.Id == 2 && t.Status == "Todo"); + var ok = Assert.IsType(result.Result); + var todos = Assert.IsAssignableFrom>(ok.Value); + Assert.Single(todos, t => t.Id == 2 && t.Status == "Todo"); } [Fact] @@ -60,7 +59,7 @@ public async Task AddTodo_ShouldReturnBadRequest_WhenTaskIdInvalid() var result = await _controller.AddTodo(1, dto); - result.Should().BeOfType(); + Assert.IsType(result); } [Fact] @@ -70,7 +69,7 @@ public async Task AddTodo_ShouldReturnNotFound_WhenEntryMissing() var result = await _controller.AddTodo(1, new AddJournalTodoDto { TaskItemId = 9 }); - result.Should().BeOfType(); + Assert.IsType(result); } [Fact] @@ -80,7 +79,7 @@ public async Task AddTodo_ShouldReturnNotFound_WhenTaskMissing() var result = await _controller.AddTodo(1, new AddJournalTodoDto { TaskItemId = 9 }); - result.Should().BeOfType(); + Assert.IsType(result); } [Fact] @@ -90,7 +89,7 @@ public async Task AddTodo_ShouldReturnConflict_WhenAlreadyLinked() var result = await _controller.AddTodo(1, new AddJournalTodoDto { TaskItemId = 4 }); - result.Should().BeOfType(); + Assert.IsType(result); } [Fact] @@ -100,7 +99,7 @@ public async Task AddTodo_ShouldReturnUnprocessableEntity_WhenEntryIsPastDay() var result = await _controller.AddTodo(1, new AddJournalTodoDto { TaskItemId = 4 }); - result.Should().BeOfType(); + Assert.IsType(result); } [Fact] @@ -110,7 +109,7 @@ public async Task AddTodo_ShouldReturnNoContent_WhenValid() var result = await _controller.AddTodo(1, new AddJournalTodoDto { TaskItemId = 4 }); - result.Should().BeOfType(); + Assert.IsType(result); _journalRepo.Verify(s => s.AddTodoAsync(1, 4), Times.Once); } @@ -122,7 +121,7 @@ public async Task RemoveTodo_ShouldReturnNoContent_WhenLinked() var result = await _controller.RemoveTodo(1, 4); - result.Should().BeOfType(); + Assert.IsType(result); _journalRepo.Verify(s => s.RemoveTodoAsync(1, 4), Times.Once); } @@ -134,7 +133,7 @@ public async Task RemoveTodo_ShouldReturnNotFound_WhenNotLinked() var result = await _controller.RemoveTodo(1, 4); - result.Should().BeOfType(); + Assert.IsType(result); } [Fact] @@ -155,10 +154,10 @@ public async Task GetAll_ShouldReturnChildren_WhenParentHasSubtasks() var result = await _controller.GetAll(1); - var ok = result.Result.Should().BeOfType().Subject; - var todos = ok.Value.Should().BeAssignableTo>().Subject.ToList(); - todos.Should().ContainSingle(t => t.Id == 2); - todos[0].Children.Should().ContainSingle(c => c.Id == 10); + var ok = Assert.IsType(result.Result); + var todos = Assert.IsAssignableFrom>(ok.Value).ToList(); + Assert.Single(todos, t => t.Id == 2); + Assert.Single(todos[0].Children!, c => c.Id == 10); } [Fact] @@ -179,10 +178,10 @@ public async Task GetAll_ShouldExcludeChildFromTopLevel_WhenParentAlsoPresent() var result = await _controller.GetAll(1); - var ok = result.Result.Should().BeOfType().Subject; - var todos = ok.Value.Should().BeAssignableTo>().Subject.ToList(); - todos.Should().ContainSingle(t => t.Id == 2); - todos[0].Children.Should().ContainSingle(c => c.Id == 10); + var ok = Assert.IsType(result.Result); + var todos = Assert.IsAssignableFrom>(ok.Value).ToList(); + Assert.Single(todos, t => t.Id == 2); + Assert.Single(todos[0].Children!, c => c.Id == 10); } [Fact] @@ -203,9 +202,9 @@ public async Task GetAll_ShouldExcludeOrphanedSubtask_WhenParentNotInEntry() var result = await _controller.GetAll(1); - var ok = result.Result.Should().BeOfType().Subject; - var todos = ok.Value.Should().BeAssignableTo>().Subject.ToList(); - todos.Should().BeEmpty(); + var ok = Assert.IsType(result.Result); + var todos = Assert.IsAssignableFrom>(ok.Value).ToList(); + Assert.Empty(todos); } [Fact] @@ -225,10 +224,10 @@ public async Task GetAll_ShouldOmitChildrenField_WhenParentHasNoSubtasks() var result = await _controller.GetAll(1); - var ok = result.Result.Should().BeOfType().Subject; - var todos = ok.Value.Should().BeAssignableTo>().Subject.ToList(); - todos.Should().ContainSingle(); - todos[0].Children.Should().BeNull(); + var ok = Assert.IsType(result.Result); + var todos = Assert.IsAssignableFrom>(ok.Value).ToList(); + Assert.Single(todos); + Assert.Null(todos[0].Children); } [Fact] @@ -260,10 +259,10 @@ public async Task GetAll_ShouldSetCurrentJournalDate_OnChildDto() var result = await _controller.GetAll(1); - var ok = result.Result.Should().BeOfType().Subject; - var todos = ok.Value.Should().BeAssignableTo>().Subject.ToList(); + var ok = Assert.IsType(result.Result); + var todos = Assert.IsAssignableFrom>(ok.Value).ToList(); var childDto = todos[0].Children!.Single(); - childDto.CurrentJournalDate.Should().Be(childJournalDate); + Assert.Equal(childJournalDate, childDto.CurrentJournalDate); } [Fact] @@ -297,11 +296,11 @@ public async Task GetAll_ShouldComputeDaysTagged_ForChildIndependently() var result = await _controller.GetAll(1); - var ok = result.Result.Should().BeOfType().Subject; - var todos = ok.Value.Should().BeAssignableTo>().Subject.ToList(); + var ok = Assert.IsType(result.Result); + var todos = Assert.IsAssignableFrom>(ok.Value).ToList(); var childDto = todos[0].Children!.Single(); // DaysTagged = childJournalDate.DayNumber - childFirstTaggedDate.DayNumber + 1 = 3 - childDto.DaysTagged.Should().Be(3); + Assert.Equal(3, childDto.DaysTagged); } [Fact] @@ -332,13 +331,13 @@ public async Task GetAll_ShouldNotIncludeOrphanedSubtask_InAnyChildList() var result = await _controller.GetAll(1); - var ok = result.Result.Should().BeOfType().Subject; - var todos = ok.Value.Should().BeAssignableTo>().Subject.ToList(); + var ok = Assert.IsType(result.Result); + var todos = Assert.IsAssignableFrom>(ok.Value).ToList(); // Parent is a root item, but its misconfigured child should still appear mapped under Children // because MapTodo iterates ChildTaskItems directly from the EF nav property. // The important assertion: the child does NOT appear in the root list. - todos.Should().ContainSingle(t => t.Id == 2); - todos.Should().NotContain(t => t.Id == 10); + Assert.Single(todos, t => t.Id == 2); + Assert.DoesNotContain(todos, t => t.Id == 10); } [Fact] @@ -353,7 +352,7 @@ public async Task RemoveTodo_ShouldAlsoRemoveChildren_WhenParentIsRemoved() var result = await _controller.RemoveTodo(1, 4); - result.Should().BeOfType(); + Assert.IsType(result); _journalRepo.Verify(s => s.RemoveTodoAsync(1, 10), Times.Once); } @@ -368,7 +367,7 @@ public async Task RemoveTodo_ShouldNotRemoveUnrelatedTodos_WhenParentIsRemoved() var result = await _controller.RemoveTodo(1, 4); - result.Should().BeOfType(); + Assert.IsType(result); _journalRepo.Verify(s => s.RemoveTodoAsync(1, 20), Times.Never); } @@ -391,9 +390,9 @@ public async Task GetAll_MapTodo_ReturnsCorrectChildTaskCount() var result = await _controller.GetAll(1); - var ok = result.Result.Should().BeOfType().Subject; - var todos = ok.Value.Should().BeAssignableTo>().Subject.ToList(); - todos.Single(t => t.Id == 2).ChildTaskCount.Should().Be(2); + var ok = Assert.IsType(result.Result); + var todos = Assert.IsAssignableFrom>(ok.Value).ToList(); + Assert.Equal(2, todos.Single(t => t.Id == 2).ChildTaskCount); } [Fact] @@ -413,10 +412,10 @@ public async Task GetAll_MapTodo_LeafTodoHasZeroChildTaskCount() var result = await _controller.GetAll(1); - var ok = result.Result.Should().BeOfType().Subject; - var todos = ok.Value.Should().BeAssignableTo>().Subject.ToList(); + var ok = Assert.IsType(result.Result); + var todos = Assert.IsAssignableFrom>(ok.Value).ToList(); var dto = todos.Single(t => t.Id == 2); - dto.ChildTaskCount.Should().Be(0); - dto.Children.Should().BeNull(); + Assert.Equal(0, dto.ChildTaskCount); + Assert.Null(dto.Children); } } diff --git a/TaskFlow.Api.Tests/Controllers/V1/NotesControllerV1Tests.cs b/TaskFlow.Api.Tests/Controllers/V1/NotesControllerV1Tests.cs index 3659782d..3613e618 100644 --- a/TaskFlow.Api.Tests/Controllers/V1/NotesControllerV1Tests.cs +++ b/TaskFlow.Api.Tests/Controllers/V1/NotesControllerV1Tests.cs @@ -1,4 +1,3 @@ -using FluentAssertions; using FluentValidation; using FluentValidation.Results; using Microsoft.AspNetCore.Mvc; @@ -41,9 +40,9 @@ public async Task GetAll_ShouldReturnOkWithNotes_WhenTaskExists() var result = await _controller.GetAll(1); - var ok = result.Result.Should().BeOfType().Subject; - var dtos = ok.Value.Should().BeAssignableTo>().Subject; - dtos.Should().HaveCount(2); + var ok = Assert.IsType(result.Result); + var dtos = Assert.IsAssignableFrom>(ok.Value); + Assert.Equal(2, dtos.Count()); _mockNoteRepo.Verify(r => r.GetAllByTaskIdAsync(1), Times.Once); } @@ -54,7 +53,7 @@ public async Task GetAll_ShouldReturnNotFound_WhenTaskDoesNotExist() var result = await _controller.GetAll(99); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); _mockNoteRepo.Verify(r => r.GetAllByTaskIdAsync(It.IsAny()), Times.Never); } @@ -70,11 +69,11 @@ public async Task Get_ShouldReturnOkWithNote_WhenNoteExists() var result = await _controller.Get(1, 5); - var ok = result.Result.Should().BeOfType().Subject; - var dto = ok.Value.Should().BeOfType().Subject; - dto.Id.Should().Be(5); - dto.Content.Should().Be("My note"); - dto.TaskItemId.Should().Be(1); + var ok = Assert.IsType(result.Result); + var dto = Assert.IsType(ok.Value); + Assert.Equal(5, dto.Id); + Assert.Equal("My note", dto.Content); + Assert.Equal(1, dto.TaskItemId); } [Fact] @@ -84,7 +83,7 @@ public async Task Get_ShouldReturnNotFound_WhenTaskDoesNotExist() var result = await _controller.Get(99, 1); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); } [Fact] @@ -96,7 +95,7 @@ public async Task Get_ShouldReturnNotFound_WhenNoteDoesNotExist() var result = await _controller.Get(1, 99); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); } // --- POST --- @@ -114,11 +113,11 @@ public async Task Create_ShouldReturnCreated_WhenValid() var result = await _controller.Create(1, createDto); - var createdResult = result.Result.Should().BeOfType().Subject; - var dto = createdResult.Value.Should().BeOfType().Subject; - dto.Id.Should().Be(3); - dto.Content.Should().Be("New note"); - dto.TaskItemId.Should().Be(1); + var createdResult = Assert.IsType(result.Result); + var dto = Assert.IsType(createdResult.Value); + Assert.Equal(3, dto.Id); + Assert.Equal("New note", dto.Content); + Assert.Equal(1, dto.TaskItemId); } [Fact] @@ -128,7 +127,7 @@ public async Task Create_ShouldReturnNotFound_WhenTaskDoesNotExist() var result = await _controller.Create(99, new CreateNoteDto { Content = "Note" }); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); _mockNoteRepo.Verify(r => r.AddAsync(It.IsAny()), Times.Never); } @@ -143,7 +142,7 @@ public async Task Create_ShouldReturnBadRequest_WhenValidationFails() var result = await _controller.Create(1, new CreateNoteDto { Content = string.Empty }); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); _mockNoteRepo.Verify(r => r.AddAsync(It.IsAny()), Times.Never); } @@ -162,9 +161,9 @@ public async Task Update_ShouldReturnOk_WhenValid() var result = await _controller.Update(1, 5, new UpdateNoteDto { Content = "Updated" }); - var ok = result.Result.Should().BeOfType().Subject; - var dto = ok.Value.Should().BeOfType().Subject; - dto.Content.Should().Be("Updated"); + var ok = Assert.IsType(result.Result); + var dto = Assert.IsType(ok.Value); + Assert.Equal("Updated", dto.Content); } [Fact] @@ -174,7 +173,7 @@ public async Task Update_ShouldReturnNotFound_WhenTaskDoesNotExist() var result = await _controller.Update(99, 1, new UpdateNoteDto { Content = "x" }); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); } [Fact] @@ -186,7 +185,7 @@ public async Task Update_ShouldReturnNotFound_WhenNoteDoesNotExist() var result = await _controller.Update(1, 99, new UpdateNoteDto { Content = "x" }); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); } // --- DELETE --- @@ -202,7 +201,7 @@ public async Task Delete_ShouldReturnNoContent_WhenNoteExists() var result = await _controller.Delete(1, 5); - result.Should().BeOfType(); + Assert.IsType(result); _mockNoteRepo.Verify(r => r.DeleteAsync(1, 5), Times.Once); } @@ -213,7 +212,7 @@ public async Task Delete_ShouldReturnNotFound_WhenTaskDoesNotExist() var result = await _controller.Delete(99, 1); - result.Should().BeOfType(); + Assert.IsType(result); } [Fact] @@ -225,6 +224,6 @@ public async Task Delete_ShouldReturnNotFound_WhenNoteDoesNotExist() var result = await _controller.Delete(1, 99); - result.Should().BeOfType(); + Assert.IsType(result); } } diff --git a/TaskFlow.Api.Tests/Controllers/V1/ProjectsControllerV1Tests.cs b/TaskFlow.Api.Tests/Controllers/V1/ProjectsControllerV1Tests.cs index afd7913c..619477fe 100644 --- a/TaskFlow.Api.Tests/Controllers/V1/ProjectsControllerV1Tests.cs +++ b/TaskFlow.Api.Tests/Controllers/V1/ProjectsControllerV1Tests.cs @@ -1,4 +1,3 @@ -using FluentAssertions; using FluentValidation; using FluentValidation.Results; using Microsoft.AspNetCore.Mvc; @@ -45,10 +44,10 @@ public async Task GetAll_ShouldReturnProjectsWithTaskCounts() var result = await _controller.GetAll(); - var ok = result.Result.Should().BeOfType().Subject; - var dto = ok.Value.Should().BeAssignableTo>().Subject.Single(); - dto.OpenTaskCount.Should().Be(1); - dto.CompletedTaskCount.Should().Be(2); + var ok = Assert.IsType(result.Result); + var dto = Assert.IsAssignableFrom>(ok.Value).Single(); + Assert.Equal(1, dto.OpenTaskCount); + Assert.Equal(2, dto.CompletedTaskCount); } [Fact] @@ -58,7 +57,7 @@ public async Task Get_ShouldReturnNotFound_WhenMissing() var result = await _controller.Get(9); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); } [Fact] @@ -68,8 +67,8 @@ public async Task Get_ShouldReturnOk_WhenFound() var result = await _controller.Get(1); - var ok = result.Result.Should().BeOfType().Subject; - ok.Value.Should().BeOfType().Which.Name.Should().Be("Alpha"); + var ok = Assert.IsType(result.Result); + Assert.Equal("Alpha", Assert.IsType(ok.Value).Name); } [Fact] @@ -79,7 +78,7 @@ public async Task GetTasks_ShouldReturnNotFound_WhenProjectMissing() var result = await _controller.GetTasks(9); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); } [Fact] @@ -92,12 +91,12 @@ public async Task GetTasks_ShouldReturnMappedTasks() var result = await _controller.GetTasks(1); - var ok = result.Result.Should().BeOfType().Subject; - var dto = ok.Value.Should().BeAssignableTo>().Subject.Single(); - dto.ProjectId.Should().Be(1); - dto.ProjectName.Should().Be("Alpha"); - dto.Priority.Should().Be("High"); - dto.Status.Should().Be("Todo"); + var ok = Assert.IsType(result.Result); + var dto = Assert.IsAssignableFrom>(ok.Value).Single(); + Assert.Equal(1, dto.ProjectId); + Assert.Equal("Alpha", dto.ProjectName); + Assert.Equal("High", dto.Priority); + Assert.Equal("Todo", dto.Status); } [Fact] @@ -112,10 +111,11 @@ public async Task Create_ShouldReturnCreated_WhenValid() var result = await _controller.Create(new CreateProjectDto { Name = " Alpha ", Description = "d" }); - var created = result.Result.Should().BeOfType().Subject; - created.RouteName.Should().Be("GetProjectV1"); - created.RouteValues.Should().ContainKey("id").WhoseValue.Should().Be(5); - created.Value.Should().BeOfType().Which.Name.Should().Be("Alpha"); + var created = Assert.IsType(result.Result); + Assert.Equal("GetProjectV1", created.RouteName); + Assert.True(created.RouteValues!.TryGetValue("id", out var createdId)); + Assert.Equal(5, createdId); + Assert.Equal("Alpha", Assert.IsType(created.Value).Name); } [Fact] @@ -125,7 +125,7 @@ public async Task Create_ShouldReturnBadRequest_WhenValidationFails() var result = await _controller.Create(new CreateProjectDto { Name = "" }); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); _mockRepo.Verify(r => r.AddAsync(It.IsAny()), Times.Never); } @@ -136,7 +136,7 @@ public async Task Create_ShouldReturnConflict_WhenNameExists() var result = await _controller.Create(new CreateProjectDto { Name = "Alpha" }); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); _mockRepo.Verify(r => r.AddAsync(It.IsAny()), Times.Never); } @@ -147,7 +147,7 @@ public async Task Update_ShouldReturnNotFound_WhenMissing() var result = await _controller.Update(9, new UpdateProjectDto { Name = "X" }); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); } [Fact] @@ -159,9 +159,9 @@ public async Task Update_ShouldApplyChangesAndArchive() var result = await _controller.Update(1, new UpdateProjectDto { Name = "New", Description = "d", IsArchived = true }); - result.Result.Should().BeOfType(); - existing.Name.Should().Be("New"); - existing.IsArchived.Should().BeTrue(); + Assert.IsType(result.Result); + Assert.Equal("New", existing.Name); + Assert.True(existing.IsArchived); _mockRepo.Verify(r => r.UpdateAsync(existing), Times.Once); } @@ -173,7 +173,7 @@ public async Task Update_ShouldReturnBadRequest_WhenValidationFails() var result = await _controller.Update(1, new UpdateProjectDto { Name = "" }); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); _mockRepo.Verify(r => r.UpdateAsync(It.IsAny()), Times.Never); } @@ -185,7 +185,7 @@ public async Task Update_ShouldReturnConflict_WhenNameTakenByAnotherProject() var result = await _controller.Update(1, new UpdateProjectDto { Name = "Taken" }); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); _mockRepo.Verify(r => r.UpdateAsync(It.IsAny()), Times.Never); } @@ -196,7 +196,7 @@ public async Task Delete_ShouldReturnNotFound_WhenMissing() var result = await _controller.Delete(9); - result.Should().BeOfType(); + Assert.IsType(result); } [Fact] @@ -206,7 +206,7 @@ public async Task Delete_ShouldReturnNoContent_WhenFound() var result = await _controller.Delete(1); - result.Should().BeOfType(); + Assert.IsType(result); _mockRepo.Verify(r => r.DeleteAsync(1), Times.Once); } @@ -221,7 +221,7 @@ public async Task AssignTask_ShouldMapRepositoryResult(AssignTaskResult repoResu var result = await _controller.AssignTask(1, new AssignProjectTaskDto { TaskItemId = 2 }); - result.Should().BeOfType(expected); + Assert.IsType(expected, result); } // TaskFlow.Web (lib/project-errors.ts) recognises the 409 conflict by this message, because @@ -233,9 +233,9 @@ public async Task AssignTask_ConflictMessage_ShouldMatchPatternUsedByFrontend() var result = await _controller.AssignTask(1, new AssignProjectTaskDto { TaskItemId = 2 }); - var conflict = result.Should().BeOfType().Subject; + var conflict = Assert.IsType(result); var message = conflict.Value!.GetType().GetProperty("message")!.GetValue(conflict.Value) as string; - message.Should().MatchRegex("(?i)already (assigned|in)"); + Assert.Matches("(?i)already (assigned|in)", message); } [Fact] @@ -245,7 +245,7 @@ public async Task AssignTask_ShouldReturnBadRequest_WhenValidationFails() var result = await _controller.AssignTask(1, new AssignProjectTaskDto { TaskItemId = 0 }); - result.Should().BeOfType(); + Assert.IsType(result); _mockRepo.Verify(r => r.AssignTaskAsync(It.IsAny(), It.IsAny()), Times.Never); } @@ -256,7 +256,7 @@ public async Task UnassignTask_ShouldReturnNoContent_WhenRemoved() var result = await _controller.UnassignTask(1, 2); - result.Should().BeOfType(); + Assert.IsType(result); } [Fact] @@ -266,6 +266,6 @@ public async Task UnassignTask_ShouldReturnNotFound_WhenTaskNotInProject() var result = await _controller.UnassignTask(1, 2); - result.Should().BeOfType(); + Assert.IsType(result); } } diff --git a/TaskFlow.Api.Tests/Controllers/V1/TaskItemsControllerV1Tests.cs b/TaskFlow.Api.Tests/Controllers/V1/TaskItemsControllerV1Tests.cs index 9dc8eb27..0b91a754 100644 --- a/TaskFlow.Api.Tests/Controllers/V1/TaskItemsControllerV1Tests.cs +++ b/TaskFlow.Api.Tests/Controllers/V1/TaskItemsControllerV1Tests.cs @@ -1,4 +1,3 @@ -using FluentAssertions; using FluentValidation; using FluentValidation.Results; using Microsoft.AspNetCore.Mvc; @@ -50,11 +49,11 @@ public async Task GetAll_ShouldReturnOkWithAllTasks() var result = await _controller.GetAll(); // Assert - var okResult = result.Result.Should().BeOfType().Subject; - var dtos = okResult.Value.Should().BeAssignableTo>().Subject; - dtos.Should().HaveCount(2); - dtos.Should().Contain(d => d.Id == 1 && d.Title == "Task 1" && d.Status == "Todo" && d.Priority == "Low" && d.DueDate == dueDate); - dtos.Should().Contain(d => d.Id == 2 && d.Title == "Task 2" && d.Status == "Completed" && d.Priority == "High" && d.DueDate == null); + var okResult = Assert.IsType(result.Result); + var dtos = Assert.IsAssignableFrom>(okResult.Value); + Assert.Equal(2, dtos.Count()); + Assert.Contains(dtos, d => d.Id == 1 && d.Title == "Task 1" && d.Status == "Todo" && d.Priority == "Low" && d.DueDate == dueDate); + Assert.Contains(dtos, d => d.Id == 2 && d.Title == "Task 2" && d.Status == "Completed" && d.Priority == "High" && d.DueDate == null); _mockRepo.Verify(r => r.GetAllAsync(), Times.Once); } @@ -79,15 +78,15 @@ public async Task Get_ShouldReturnOkWithTask_WhenTaskExists() var result = await _controller.Get(1); // Assert - var okResult = result.Result.Should().BeOfType().Subject; - var dto = okResult.Value.Should().BeOfType().Subject; - dto.Id.Should().Be(1); - dto.Title.Should().Be("Task 1"); - dto.Description.Should().Be("Description"); - dto.IsComplete.Should().BeFalse(); - dto.Status.Should().Be("Todo"); - dto.Priority.Should().Be("Medium"); - dto.DueDate.Should().Be(dueDate); + var okResult = Assert.IsType(result.Result); + var dto = Assert.IsType(okResult.Value); + Assert.Equal(1, dto.Id); + Assert.Equal("Task 1", dto.Title); + Assert.Equal("Description", dto.Description); + Assert.False(dto.IsComplete); + Assert.Equal("Todo", dto.Status); + Assert.Equal("Medium", dto.Priority); + Assert.Equal(dueDate, dto.DueDate); _mockRepo.Verify(r => r.GetByIdAsync(1), Times.Once); } @@ -101,7 +100,7 @@ public async Task Get_ShouldReturnNotFound_WhenTaskDoesNotExist() var result = await _controller.Get(1); // Assert - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); _mockRepo.Verify(r => r.GetByIdAsync(1), Times.Once); } @@ -138,20 +137,22 @@ public async Task Create_ShouldReturnCreatedAtRoute_WhenValid() var result = await _controller.Create(createDto); // Assert - var createdResult = result.Result.Should().BeOfType().Subject; - createdResult.RouteName.Should().Be("GetTaskV1"); - createdResult.RouteValues.Should().ContainKey("version").WhoseValue.Should().Be("1.0"); - createdResult.RouteValues.Should().ContainKey("id").WhoseValue.Should().Be(1); + var createdResult = Assert.IsType(result.Result); + Assert.Equal("GetTaskV1", createdResult.RouteName); + Assert.True(createdResult.RouteValues!.TryGetValue("version", out var versionValue)); + Assert.Equal("1.0", versionValue); + Assert.True(createdResult.RouteValues.TryGetValue("id", out var idValue)); + Assert.Equal(1, idValue); // Controller now returns TaskItemResponseDto, not TaskItem - var responseDto = createdResult.Value.Should().BeOfType().Subject; - responseDto.Id.Should().Be(1); - responseDto.Title.Should().Be("New Task"); - responseDto.Description.Should().Be("New Description"); - responseDto.IsComplete.Should().BeFalse(); - responseDto.Status.Should().Be("Todo"); - responseDto.Priority.Should().Be("High"); - responseDto.DueDate.Should().Be(dueDate); + var responseDto = Assert.IsType(createdResult.Value); + Assert.Equal(1, responseDto.Id); + Assert.Equal("New Task", responseDto.Title); + Assert.Equal("New Description", responseDto.Description); + Assert.False(responseDto.IsComplete); + Assert.Equal("Todo", responseDto.Status); + Assert.Equal("High", responseDto.Priority); + Assert.Equal(dueDate, responseDto.DueDate); } [Fact] @@ -175,12 +176,12 @@ public async Task Create_ShouldAssignProject_WhenProjectExists() var result = await _controller.Create(createDto); - captured.Should().NotBeNull(); - captured!.ProjectId.Should().Be(7); - var created = result.Result.Should().BeOfType().Subject; - var dto = created.Value.Should().BeOfType().Subject; - dto.ProjectId.Should().Be(7); - dto.ProjectName.Should().Be("Alpha"); + Assert.NotNull(captured); + Assert.Equal(7, captured!.ProjectId); + var created = Assert.IsType(result.Result); + var dto = Assert.IsType(created.Value); + Assert.Equal(7, dto.ProjectId); + Assert.Equal("Alpha", dto.ProjectName); } [Fact] @@ -191,9 +192,9 @@ public async Task Create_ShouldReturnUnprocessableEntity_WhenProjectDoesNotExist var result = await _controller.Create(createDto); - var unprocessable = result.Result.Should().BeOfType().Subject; - unprocessable.Value.Should().NotBeNull(); - unprocessable.Value!.ToString().Should().Contain("TASK_PROJECT_NOT_FOUND"); + var unprocessable = Assert.IsType(result.Result); + Assert.NotNull(unprocessable.Value); + Assert.Contains("TASK_PROJECT_NOT_FOUND", unprocessable.Value!.ToString()); _mockRepo.Verify(r => r.AddAsync(It.IsAny()), Times.Never); } @@ -207,10 +208,10 @@ public async Task Update_ShouldPreserveProject() var result = await _controller.Update(3, new UpdateTaskItemDto { Title = "New" }); - var ok = result.Result.Should().BeOfType().Subject; - var dto = ok.Value.Should().BeOfType().Subject; - dto.ProjectId.Should().Be(7); - existing.ProjectId.Should().Be(7); + var ok = Assert.IsType(result.Result); + var dto = Assert.IsType(ok.Value); + Assert.Equal(7, dto.ProjectId); + Assert.Equal(7, existing.ProjectId); } [Fact] @@ -227,7 +228,7 @@ public async Task Create_ShouldReturnUnprocessableEntity_WhenParentTaskDoesNotEx var result = await _controller.Create(createDto); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); _mockRepo.Verify(r => r.AddAsync(It.IsAny()), Times.Never); } @@ -242,7 +243,7 @@ public async Task Create_ShouldReturnUnprocessableEntity_WhenJournalDateIsPast() var result = await _controller.Create(createDto); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); _mockRepo.Verify(r => r.AddAsync(It.IsAny()), Times.Never); } @@ -263,7 +264,7 @@ public async Task Create_ShouldReturnUnprocessableEntity_WhenJournalDateIsYester var result = await _controller.Create(createDto); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); _mockRepo.Verify(r => r.AddAsync(It.IsAny()), Times.Never); } @@ -294,7 +295,7 @@ public async Task Create_ShouldAssignToJournalDate_WhenJournalDateIsProvided() var result = await _controller.Create(createDto); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); _mockJournalRepo.Verify(r => r.AddTodoAsync(entry.Id, createdTask.Id), Times.Once); } @@ -331,7 +332,7 @@ public async Task Create_ShouldSucceedAndAttachToExistingEntry_WhenJournalDateRa var result = await _controller.Create(createDto); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); _mockJournalRepo.Verify(r => r.AddTodoAsync(existingEntry.Id, createdTask.Id, null), Times.Once); _mockJournalRepo.Verify(r => r.GetByDateAsync(targetDate), Times.Exactly(2)); } @@ -350,7 +351,7 @@ public async Task Create_ShouldNeverTriggerCarryForward_WhenJournalDateIsProvide var result = await _controller.Create(new CreateTaskItemDto { Title = "Tomorrow", JournalDate = targetDate, TimezoneOffsetMinutes = 0 }); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); _mockJournalRepo.Verify(r => r.CarryForwardIncompleteTasksAsync(It.IsAny(), It.IsAny()), Times.Never); } @@ -383,11 +384,11 @@ public async Task Create_ShouldLeaveTodaysTasksOnToday_WhenTaskCreatedForTomorro TimezoneOffsetMinutes = offset }); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); var tomorrowEntry = await journalRepo.GetByDateAsync(today.AddDays(1)); - tomorrowEntry!.Todos.Select(t => t.Title).Should().BeEquivalentTo(["Tomorrow task"]); - (await journalRepo.GetTodosAsync(todayEntry.Id)).Should().ContainSingle(t => t.Id == todayTask.Id); - context.TaskItems.Single(t => t.Id == todayTask.Id).MoveCount.Should().Be(0); + Assert.Equal("Tomorrow task", Assert.Single(tomorrowEntry!.Todos.Select(t => t.Title))); + Assert.Single((await journalRepo.GetTodosAsync(todayEntry.Id)), t => t.Id == todayTask.Id); + Assert.Equal(0, context.TaskItems.Single(t => t.Id == todayTask.Id).MoveCount); } [Fact] @@ -423,7 +424,7 @@ public async Task Create_ShouldNotMoveOtherTasks_WhenJournalDateIsTomorrowAndTod var before = seeded.ToDictionary(t => t.Id, t => (t.CurrentJournalEntryId, t.MoveCount, t.FirstTaggedDate)); var eventsBefore = await context.TaskItemEvents.CountAsync(); - (await journalRepo.GetByDateAsync(tomorrow)).Should().BeNull(); + Assert.Null((await journalRepo.GetByDateAsync(tomorrow))); var result = await controller.Create(new CreateTaskItemDto { @@ -432,20 +433,19 @@ public async Task Create_ShouldNotMoveOtherTasks_WhenJournalDateIsTomorrowAndTod TimezoneOffsetMinutes = offset }); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); var tomorrowEntry = await journalRepo.GetByDateAsync(tomorrow); - tomorrowEntry.Should().NotBeNull(); - tomorrowEntry!.Todos.Select(t => t.Title).Should().BeEquivalentTo(["Tomorrow task"]); + Assert.NotNull(tomorrowEntry); + Assert.Equal("Tomorrow task", Assert.Single(tomorrowEntry!.Todos.Select(t => t.Title))); foreach (var task in seeded) { var reloaded = await context.TaskItems.AsNoTracking().SingleAsync(t => t.Id == task.Id); - (reloaded.CurrentJournalEntryId, reloaded.MoveCount, reloaded.FirstTaggedDate).Should().Be(before[task.Id]); + Assert.Equal(before[task.Id], (reloaded.CurrentJournalEntryId, reloaded.MoveCount, reloaded.FirstTaggedDate)); } var seededIds = seeded.Select(t => t.Id).ToList(); - (await context.TaskItemEvents.CountAsync(e => seededIds.Contains(e.TaskItemId) && e.EventType == "ReassignedToJournalDay")) - .Should().Be(0); - (await context.TaskItemEvents.CountAsync(e => seededIds.Contains(e.TaskItemId))).Should().Be(eventsBefore); + Assert.Equal(0, await context.TaskItemEvents.CountAsync(e => seededIds.Contains(e.TaskItemId) && e.EventType == "ReassignedToJournalDay")); + Assert.Equal(eventsBefore, (await context.TaskItemEvents.CountAsync(e => seededIds.Contains(e.TaskItemId)))); _mockJournalRepo.Verify(r => r.CarryForwardIncompleteTasksAsync(It.IsAny(), It.IsAny()), Times.Never); } @@ -477,7 +477,7 @@ public async Task Create_ShouldForwardTimezoneOffsetToAddTodoAsync_WhenJournalDa var result = await _controller.Create(createDto); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); _mockJournalRepo.Verify(r => r.AddTodoAsync(entry.Id, createdTask.Id, offsetMinutes), Times.Once); } @@ -500,7 +500,7 @@ public async Task Create_ShouldReturnUnprocessableEntity_WhenParentWouldExceedOn var result = await _controller.Create(createDto); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); _mockRepo.Verify(r => r.AddAsync(It.IsAny()), Times.Never); } @@ -537,15 +537,15 @@ public async Task Update_ShouldReturnOkWithUpdatedTask_WhenValid() // Assert // Controller now returns 200 OK with TaskItemResponseDto instead of 204 NoContent - var okResult = result.Result.Should().BeOfType().Subject; - var responseDto = okResult.Value.Should().BeOfType().Subject; - responseDto.Id.Should().Be(1); - responseDto.Title.Should().Be("Updated Task"); - responseDto.Description.Should().Be("Updated Description"); - responseDto.IsComplete.Should().BeTrue(); - responseDto.Status.Should().Be("Completed"); - responseDto.Priority.Should().Be("Medium"); - responseDto.DueDate.Should().Be(dueDate); + var okResult = Assert.IsType(result.Result); + var responseDto = Assert.IsType(okResult.Value); + Assert.Equal(1, responseDto.Id); + Assert.Equal("Updated Task", responseDto.Title); + Assert.Equal("Updated Description", responseDto.Description); + Assert.True(responseDto.IsComplete); + Assert.Equal("Completed", responseDto.Status); + Assert.Equal("Medium", responseDto.Priority); + Assert.Equal(dueDate, responseDto.DueDate); _mockRepo.Verify(r => r.UpdateAsync(It.IsAny()), Times.Once); } @@ -561,7 +561,7 @@ public async Task Delete_ShouldReturnNoContent_WhenTaskExists() var result = await _controller.Delete(1); // Assert - result.Should().BeOfType(); + Assert.IsType(result); _mockRepo.Verify(r => r.DeleteAsync(1), Times.Once); } @@ -575,7 +575,7 @@ public async Task Delete_ShouldReturnNotFound_WhenTaskDoesNotExist() var result = await _controller.Delete(1); // Assert - result.Should().BeOfType(); + Assert.IsType(result); _mockRepo.Verify(r => r.DeleteAsync(It.IsAny()), Times.Never); } @@ -589,9 +589,9 @@ public async Task GetAll_ShouldReturnOkWithEmptyList_WhenNoTasks() var result = await _controller.GetAll(); // Assert - var okResult = result.Result.Should().BeOfType().Subject; - var dtos = okResult.Value.Should().BeAssignableTo>().Subject; - dtos.Should().BeEmpty(); + var okResult = Assert.IsType(result.Result); + var dtos = Assert.IsAssignableFrom>(okResult.Value); + Assert.Empty(dtos); _mockRepo.Verify(r => r.GetAllAsync(), Times.Once); } @@ -611,8 +611,8 @@ public async Task Create_ShouldReturnBadRequest_WhenValidationFails() var result = await _controller.Create(createDto); // Assert - var badRequestResult = result.Result.Should().BeOfType().Subject; - badRequestResult.Value.Should().BeEquivalentTo(validationFailures); + var badRequestResult = Assert.IsType(result.Result); + Assert.Equivalent(validationFailures, badRequestResult.Value, strict: true); _mockRepo.Verify(r => r.AddAsync(It.IsAny()), Times.Never); } @@ -630,12 +630,12 @@ public async Task Create_ShouldHandleTaskWithNullDescription() var result = await _controller.Create(createDto); // Assert - var createdResult = result.Result.Should().BeOfType().Subject; - var dto = createdResult.Value.Should().BeOfType().Subject; - dto.Title.Should().Be("Task"); - dto.Description.Should().BeNull(); - dto.IsComplete.Should().BeFalse(); - dto.Status.Should().Be("Draft"); + var createdResult = Assert.IsType(result.Result); + var dto = Assert.IsType(createdResult.Value); + Assert.Equal("Task", dto.Title); + Assert.Null(dto.Description); + Assert.False(dto.IsComplete); + Assert.Equal("Draft", dto.Status); } [Fact] @@ -649,7 +649,7 @@ public async Task Update_ShouldReturnNotFound_WhenTaskDoesNotExist() var result = await _controller.Update(999, updateDto); // Assert - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); _mockRepo.Verify(r => r.GetByIdAsync(999), Times.Once); _mockRepo.Verify(r => r.UpdateAsync(It.IsAny()), Times.Never); } @@ -672,8 +672,8 @@ public async Task Update_ShouldReturnBadRequest_WhenValidationFails() var result = await _controller.Update(1, updateDto); // Assert - var badRequestResult = result.Result.Should().BeOfType().Subject; - badRequestResult.Value.Should().BeEquivalentTo(validationFailures); + var badRequestResult = Assert.IsType(result.Result); + Assert.Equivalent(validationFailures, badRequestResult.Value, strict: true); _mockRepo.Verify(r => r.UpdateAsync(It.IsAny()), Times.Never); } @@ -707,9 +707,9 @@ public async Task Update_ShouldPreserveExistingPriority_WhenPriorityOmitted() var result = await _controller.Update(1, updateDto); // Assert - var okResult = result.Result.Should().BeOfType().Subject; - var responseDto = okResult.Value.Should().BeOfType().Subject; - responseDto.Priority.Should().Be("High"); // Priority preserved from existing task + var okResult = Assert.IsType(result.Result); + var responseDto = Assert.IsType(okResult.Value); + Assert.Equal("High", responseDto.Priority); // Priority preserved from existing task _mockRepo.Verify(r => r.UpdateAsync(It.IsAny()), Times.Once); } @@ -743,8 +743,8 @@ public async Task Update_ShouldReturnUnprocessableEntity_WhenReopeningTaskAssign var result = await _controller.Update(1, updateDto); // Assert - var unprocessable = result.Result.Should().BeOfType().Subject; - unprocessable.Value.Should().NotBeNull(); + var unprocessable = Assert.IsType(result.Result); + Assert.NotNull(unprocessable.Value); _mockRepo.Verify(r => r.UpdateAsync(It.IsAny()), Times.Never); } @@ -781,7 +781,7 @@ public async Task Update_ShouldAllowReopeningTaskAssignedToToday() var result = await _controller.Update(1, updateDto); // Assert - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); _mockRepo.Verify(r => r.UpdateAsync(It.IsAny()), Times.Once); } @@ -819,7 +819,7 @@ public async Task Update_ShouldAllowReopeningTaskAssignedToUserLocalToday_WhenOf var result = await _controller.Update(1, updateDto); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); _mockRepo.Verify(r => r.UpdateAsync(It.IsAny()), Times.Once); } @@ -854,13 +854,12 @@ public async Task Update_ShouldReturnUnprocessableEntity_WhenReopeningTaskAssign var result = await _controller.Update(1, updateDto); - var unprocessable = result.Result.Should().BeOfType().Subject; + var unprocessable = Assert.IsType(result.Result); var value = unprocessable.Value!; - value.GetType().GetProperty("code")!.GetValue(value).Should().Be("TASK_REOPEN_PAST_DAY_NOT_ALLOWED"); - value.GetType().GetProperty("message")!.GetValue(value) - .Should().Be("Completed tasks assigned to past days cannot be reopened."); + Assert.Equal("TASK_REOPEN_PAST_DAY_NOT_ALLOWED", value.GetType().GetProperty("code")!.GetValue(value)); + Assert.Equal("Completed tasks assigned to past days cannot be reopened.", value.GetType().GetProperty("message")!.GetValue(value)); var details = value.GetType().GetProperty("details")!.GetValue(value)!; - details.GetType().GetProperty("assignedDate")!.GetValue(details).Should().Be(assignedDate); + Assert.Equal(assignedDate, details.GetType().GetProperty("assignedDate")!.GetValue(details)); _mockRepo.Verify(r => r.UpdateAsync(It.IsAny()), Times.Never); } @@ -891,13 +890,13 @@ public async Task Update_ShouldReturnUnprocessableEntity_WhenAssignedDateIsUtcTo var utcNow = DateTime.UtcNow; var assignedDate = DateOnly.FromDateTime(utcNow); - UserDateHelper.GetUserToday(utcNow, offset).Should().Be(assignedDate.AddDays(1)); + Assert.Equal(assignedDate.AddDays(1), UserDateHelper.GetUserToday(utcNow, offset)); _mockRepo.Setup(r => r.GetByIdAsync(1)).ReturnsAsync(existingTask); _mockRepo.Setup(r => r.GetAssignedJournalDateAsync(1)).ReturnsAsync(assignedDate); var result = await _controller.Update(1, updateDto); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); _mockRepo.Verify(r => r.UpdateAsync(It.IsAny()), Times.Never); } @@ -932,7 +931,7 @@ public async Task Update_ShouldUseUtcToday_WhenOffsetMissing_AndAllowReopeningTa var result = await _controller.Update(1, updateDto); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); _mockRepo.Verify(r => r.UpdateAsync(It.IsAny()), Times.Once); } @@ -964,9 +963,9 @@ public async Task Update_ShouldUseUtcToday_WhenOffsetMissing_AndRejectReopeningT var result = await _controller.Update(1, updateDto); - var unprocessable = result.Result.Should().BeOfType().Subject; + var unprocessable = Assert.IsType(result.Result); var value = unprocessable.Value!; - value.GetType().GetProperty("code")!.GetValue(value).Should().Be("TASK_REOPEN_PAST_DAY_NOT_ALLOWED"); + Assert.Equal("TASK_REOPEN_PAST_DAY_NOT_ALLOWED", value.GetType().GetProperty("code")!.GetValue(value)); _mockRepo.Verify(r => r.UpdateAsync(It.IsAny()), Times.Never); } @@ -997,7 +996,7 @@ public async Task Update_ShouldReturnUnprocessableEntity_WhenParentTaskMatchesSe var result = await _controller.Update(1, updateDto); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); _mockRepo.Verify(r => r.UpdateAsync(It.IsAny()), Times.Never); } @@ -1029,7 +1028,7 @@ public async Task Update_ShouldReturnUnprocessableEntity_WhenParentTaskDoesNotEx var result = await _controller.Update(1, updateDto); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); _mockRepo.Verify(r => r.UpdateAsync(It.IsAny()), Times.Never); } @@ -1066,7 +1065,7 @@ public async Task Update_ShouldReturnUnprocessableEntity_WhenParentWouldExceedOn var result = await _controller.Update(1, updateDto); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); _mockRepo.Verify(r => r.UpdateAsync(It.IsAny()), Times.Never); } @@ -1118,7 +1117,7 @@ public async Task Update_ShouldReturnUnprocessableEntity_WhenTaskWithChildrenIsA var result = await _controller.Update(1, updateDto); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); _mockRepo.Verify(r => r.UpdateAsync(It.IsAny()), Times.Never); } @@ -1160,7 +1159,7 @@ public async Task Update_ShouldReturnUnprocessableEntity_WhenCompletingParentWit var result = await _controller.Update(1, updateDto); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); _mockRepo.Verify(r => r.UpdateAsync(It.IsAny()), Times.Never); } @@ -1187,7 +1186,7 @@ public async Task Update_ShouldReturnUnprocessableEntity_WhenParentAssignmentWou var result = await _controller.Update(1, updateDto); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); _mockRepo.Verify(r => r.UpdateAsync(It.IsAny()), Times.Never); } @@ -1198,7 +1197,7 @@ public async Task GetHistory_ShouldReturnNotFound_WhenTaskDoesNotExist() var result = await _controller.GetHistory(7); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); } [Fact] @@ -1220,9 +1219,9 @@ public async Task GetHistory_ShouldReturnOk_WhenTaskExists() var result = await _controller.GetHistory(1); - var okResult = result.Result.Should().BeOfType().Subject; - var history = okResult.Value.Should().BeAssignableTo>().Subject; - history.Should().ContainSingle(e => e.Id == 10 && e.EventType == "AssignedToJournalDay"); + var okResult = Assert.IsType(result.Result); + var history = Assert.IsAssignableFrom>(okResult.Value); + Assert.Single(history, e => e.Id == 10 && e.EventType == "AssignedToJournalDay"); } [Fact] @@ -1236,7 +1235,7 @@ public async Task Delete_ShouldReturnUnprocessableEntity_WhenTaskHasChildren() var result = await _controller.Delete(1); - result.Should().BeOfType(); + Assert.IsType(result); _mockRepo.Verify(r => r.DeleteAsync(It.IsAny()), Times.Never); } @@ -1286,7 +1285,7 @@ public async Task Update_ShouldAutoCompleteParent_WhenEnabledAndLastChildComplet var result = await _controller.Update(2, updateDto); - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); _mockRepo.Verify(r => r.UpdateAsync(It.Is(t => t.Id == 2)), Times.Once); _mockRepo.Verify(r => r.UpdateAsync(It.Is(t => t.Id == 10 && t.IsComplete && t.Status == Status.Completed)), Times.Once); } @@ -1309,9 +1308,9 @@ public async Task Get_ReturnsChildTaskCount_ComputedOnce() var result = await _controller.Get(1); // Assert - var okResult = result.Result.Should().BeOfType().Subject; - var dto = okResult.Value.Should().BeOfType().Subject; - dto.ChildTaskCount.Should().Be(2); + var okResult = Assert.IsType(result.Result); + var dto = Assert.IsType(okResult.Value); + Assert.Equal(2, dto.ChildTaskCount); _mockRepo.Verify(r => r.GetAllAsync(), Times.Once); } @@ -1319,8 +1318,8 @@ public async Task Get_ReturnsChildTaskCount_ComputedOnce() public void Get_ResponseDto_DoesNotExposeChildCount_ReflectionGuard() { // Assert: ChildCount must never be reintroduced; ChildTaskCount is the sole source of truth. - typeof(TaskItemResponseDto).GetProperty("ChildCount").Should().BeNull(); - typeof(TaskItemResponseDto).GetProperty("ChildTaskCount").Should().NotBeNull(); + Assert.Null(typeof(TaskItemResponseDto).GetProperty("ChildCount")); + Assert.NotNull(typeof(TaskItemResponseDto).GetProperty("ChildTaskCount")); } [Fact] @@ -1350,9 +1349,9 @@ public async Task Create_ReturnsCorrectChildTaskCount() var result = await _controller.Create(createDto); // Assert - var createdResult = result.Result.Should().BeOfType().Subject; - var responseDto = createdResult.Value.Should().BeOfType().Subject; - responseDto.ChildTaskCount.Should().Be(2); + var createdResult = Assert.IsType(result.Result); + var responseDto = Assert.IsType(createdResult.Value); + Assert.Equal(2, responseDto.ChildTaskCount); } [Fact] @@ -1383,9 +1382,9 @@ public async Task Update_ReturnsCorrectChildTaskCount() var result = await _controller.Update(1, updateDto); // Assert - var okResult = result.Result.Should().BeOfType().Subject; - var responseDto = okResult.Value.Should().BeOfType().Subject; - responseDto.ChildTaskCount.Should().Be(3); + var okResult = Assert.IsType(result.Result); + var responseDto = Assert.IsType(okResult.Value); + Assert.Equal(3, responseDto.ChildTaskCount); } [Fact] @@ -1405,10 +1404,10 @@ public async Task GetAll_ChildTaskCount_MatchesDictionaryLookup() var result = await _controller.GetAll(); // Assert - var okResult = result.Result.Should().BeOfType().Subject; - var dtos = okResult.Value.Should().BeAssignableTo>().Subject.ToList(); - dtos.Single(d => d.Id == 1).ChildTaskCount.Should().Be(2); - dtos.Single(d => d.Id == 2).ChildTaskCount.Should().Be(0); - dtos.Single(d => d.Id == 4).ChildTaskCount.Should().Be(0); + var okResult = Assert.IsType(result.Result); + var dtos = Assert.IsAssignableFrom>(okResult.Value).ToList(); + Assert.Equal(2, dtos.Single(d => d.Id == 1).ChildTaskCount); + Assert.Equal(0, dtos.Single(d => d.Id == 2).ChildTaskCount); + Assert.Equal(0, dtos.Single(d => d.Id == 4).ChildTaskCount); } } diff --git a/TaskFlow.Api.Tests/Extensions/ApiVersioningServiceExtensionsTests.cs b/TaskFlow.Api.Tests/Extensions/ApiVersioningServiceExtensionsTests.cs index f3b98101..78c6c52c 100644 --- a/TaskFlow.Api.Tests/Extensions/ApiVersioningServiceExtensionsTests.cs +++ b/TaskFlow.Api.Tests/Extensions/ApiVersioningServiceExtensionsTests.cs @@ -1,4 +1,3 @@ -using FluentAssertions; using Microsoft.Extensions.DependencyInjection; using TaskFlow.Api.Extensions; @@ -16,10 +15,10 @@ public void AddApiVersioningConfiguration_ShouldRegisterApiVersioningServices() var result = services.AddApiVersioningConfiguration(); // Assert - result.Should().BeSameAs(services); + Assert.Same(services, result); // Verify that API versioning services are registered by checking the service collection - services.Should().Contain(s => s.ServiceType.FullName != null && + Assert.Contains(services, s => s.ServiceType.FullName != null && s.ServiceType.FullName.Contains("ApiVersion")); } @@ -33,7 +32,7 @@ public void AddApiVersioningConfiguration_ShouldReturnServiceCollection() var result = services.AddApiVersioningConfiguration(); // Assert - result.Should().NotBeNull(); - result.Should().BeOfType(); + Assert.NotNull(result); + Assert.IsType(result); } } diff --git a/TaskFlow.Api.Tests/Extensions/CorsServiceExtensionsTests.cs b/TaskFlow.Api.Tests/Extensions/CorsServiceExtensionsTests.cs index 7ce8d64d..a7d7de9d 100644 --- a/TaskFlow.Api.Tests/Extensions/CorsServiceExtensionsTests.cs +++ b/TaskFlow.Api.Tests/Extensions/CorsServiceExtensionsTests.cs @@ -1,4 +1,3 @@ -using FluentAssertions; using Microsoft.AspNetCore.Cors.Infrastructure; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -21,7 +20,7 @@ public void AddCorsPolicy_ShouldRegisterCorsServices() services.AddCorsPolicy(configuration); - services.Should().Contain(s => s.ServiceType == typeof(ICorsService)); + Assert.Contains(services, s => s.ServiceType == typeof(ICorsService)); } [Fact] @@ -37,7 +36,7 @@ public void AddCorsPolicy_ShouldReturnServiceCollection() var result = services.AddCorsPolicy(configuration); - result.Should().BeSameAs(services); + Assert.Same(services, result); } [Fact] @@ -50,7 +49,7 @@ public void AddCorsPolicy_WithNoOrigins_ShouldBeNoOp() services.AddCorsPolicy(configuration); - services.Should().NotContain(s => s.ServiceType == typeof(ICorsService)); + Assert.DoesNotContain(services, s => s.ServiceType == typeof(ICorsService)); } [Fact] @@ -66,7 +65,7 @@ public void GetConfiguredOrigins_WithOrigins_ShouldReturnOrigins() var origins = CorsServiceExtensions.GetConfiguredOrigins(configuration); - origins.Should().BeEquivalentTo(["http://localhost:5173", "http://localhost:3000"]); + Assert.Equal(new[] { "http://localhost:5173", "http://localhost:3000" }.Order(), origins.Order()); } [Fact] @@ -78,7 +77,7 @@ public void GetConfiguredOrigins_WithNoOrigins_ShouldReturnEmpty() var origins = CorsServiceExtensions.GetConfiguredOrigins(configuration); - origins.Should().BeEmpty(); + Assert.Empty(origins); } [Fact] @@ -93,6 +92,6 @@ public void GetConfiguredOrigins_WhenLoadedFromProductionAppsettings_ShouldRetur var origins = CorsServiceExtensions.GetConfiguredOrigins(configuration); - origins.Should().BeEquivalentTo(["https://taskflow.skalaforge.com"]); + Assert.Equal("https://taskflow.skalaforge.com", Assert.Single(origins)); } } diff --git a/TaskFlow.Api.Tests/Extensions/HealthCheckServiceExtensionsTests.cs b/TaskFlow.Api.Tests/Extensions/HealthCheckServiceExtensionsTests.cs index 6ec6786b..287472af 100644 --- a/TaskFlow.Api.Tests/Extensions/HealthCheckServiceExtensionsTests.cs +++ b/TaskFlow.Api.Tests/Extensions/HealthCheckServiceExtensionsTests.cs @@ -1,4 +1,3 @@ -using FluentAssertions; using Microsoft.AspNetCore.Diagnostics.HealthChecks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Diagnostics.HealthChecks; @@ -15,7 +14,7 @@ public void CreateHealthCheckOptions_ShouldSetResponseWriter() { var options = HealthCheckServiceExtensions.CreateHealthCheckOptions(); - options.ResponseWriter.Should().Be((Func)HealthCheckResponseWriter.WriteHealthCheckResponse); + Assert.Equal((Func)HealthCheckResponseWriter.WriteHealthCheckResponse, options.ResponseWriter); } [Fact] @@ -23,9 +22,9 @@ public void CreateHealthCheckOptions_ShouldConfigureStatusCodes() { var options = HealthCheckServiceExtensions.CreateHealthCheckOptions(); - options.ResultStatusCodes[HealthStatus.Healthy].Should().Be(StatusCodes.Status200OK); - options.ResultStatusCodes[HealthStatus.Degraded].Should().Be(StatusCodes.Status200OK); - options.ResultStatusCodes[HealthStatus.Unhealthy].Should().Be(StatusCodes.Status503ServiceUnavailable); + Assert.Equal(StatusCodes.Status200OK, options.ResultStatusCodes[HealthStatus.Healthy]); + Assert.Equal(StatusCodes.Status200OK, options.ResultStatusCodes[HealthStatus.Degraded]); + Assert.Equal(StatusCodes.Status503ServiceUnavailable, options.ResultStatusCodes[HealthStatus.Unhealthy]); } [Fact] @@ -33,14 +32,14 @@ public void CreateReadinessHealthCheckOptions_ShouldIncludeOnlyReadyTaggedChecks { var options = HealthCheckServiceExtensions.CreateReadinessHealthCheckOptions(); - options.Predicate.Should().NotBeNull(); + Assert.NotNull(options.Predicate); var healthCheck = Mock.Of(); var readyRegistration = new HealthCheckRegistration("database", healthCheck, null, new[] { "ready" }); var liveRegistration = new HealthCheckRegistration("self", healthCheck, null, new[] { "live" }); - options.Predicate!(readyRegistration).Should().BeTrue(); - options.Predicate!(liveRegistration).Should().BeFalse(); + Assert.True(options.Predicate!(readyRegistration)); + Assert.False(options.Predicate!(liveRegistration)); } [Fact] @@ -48,13 +47,13 @@ public void CreateLivenessHealthCheckOptions_ShouldIncludeOnlyLiveTaggedChecks() { var options = HealthCheckServiceExtensions.CreateLivenessHealthCheckOptions(); - options.Predicate.Should().NotBeNull(); + Assert.NotNull(options.Predicate); var healthCheck = Mock.Of(); var liveRegistration = new HealthCheckRegistration("self", healthCheck, null, new[] { "live" }); var readyRegistration = new HealthCheckRegistration("database", healthCheck, null, new[] { "ready" }); - options.Predicate!(liveRegistration).Should().BeTrue(); - options.Predicate!(readyRegistration).Should().BeFalse(); + Assert.True(options.Predicate!(liveRegistration)); + Assert.False(options.Predicate!(readyRegistration)); } } diff --git a/TaskFlow.Api.Tests/Extensions/OpenApiServiceExtensionsTests.cs b/TaskFlow.Api.Tests/Extensions/OpenApiServiceExtensionsTests.cs index aa6363ee..68c8f385 100644 --- a/TaskFlow.Api.Tests/Extensions/OpenApiServiceExtensionsTests.cs +++ b/TaskFlow.Api.Tests/Extensions/OpenApiServiceExtensionsTests.cs @@ -1,4 +1,3 @@ -using FluentAssertions; using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.OpenApi; using Microsoft.Extensions.DependencyInjection; @@ -30,10 +29,10 @@ public async Task ApiVersionDocumentTransformer_TransformAsync_SetsDocumentInfo_ await transformer.TransformAsync(document, context, CancellationToken.None); // Assert - document.Info.Should().NotBeNull(); - document.Info.Title.Should().Be("TaskFlow API"); - document.Info.Version.Should().Be("v1"); - document.Info.Description.Should().Contain("RESTful"); + Assert.NotNull(document.Info); + Assert.Equal("TaskFlow API", document.Info.Title); + Assert.Equal("v1", document.Info.Version); + Assert.Contains("RESTful", document.Info.Description); } [Fact] @@ -49,7 +48,7 @@ public async Task ApiVersionDocumentTransformer_TransformAsync_ReturnsCompletedT await task; // Assert - task.IsCompleted.Should().BeTrue(); - document.Info.Version.Should().Be("v2"); + Assert.True(task.IsCompleted); + Assert.Equal("v2", document.Info.Version); } } diff --git a/TaskFlow.Api.Tests/Extensions/OpenTelemetryServiceExtensionsTests.cs b/TaskFlow.Api.Tests/Extensions/OpenTelemetryServiceExtensionsTests.cs index 65a3da0c..f00848d8 100644 --- a/TaskFlow.Api.Tests/Extensions/OpenTelemetryServiceExtensionsTests.cs +++ b/TaskFlow.Api.Tests/Extensions/OpenTelemetryServiceExtensionsTests.cs @@ -1,4 +1,3 @@ -using FluentAssertions; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -28,7 +27,7 @@ public void AddOpenTelemetryObservability_WithDefaultSettings_RegistersOpenTelem services.AddOpenTelemetryObservability(config); - services.Should().Contain(s => s.ServiceType == typeof(TracerProvider)); + Assert.Contains(services, s => s.ServiceType == typeof(TracerProvider)); } [Fact] @@ -39,7 +38,7 @@ public void AddOpenTelemetryObservability_ReturnsServiceCollection() var result = services.AddOpenTelemetryObservability(config); - result.Should().BeSameAs(services); + Assert.Same(services, result); } [Fact] @@ -53,7 +52,7 @@ public void AddOpenTelemetryObservability_WithHttpProtobufProtocol_DoesNotThrow( var act = () => services.AddOpenTelemetryObservability(config); - act.Should().NotThrow(); + Assert.Null(Record.Exception(act)); } [Fact] @@ -67,8 +66,8 @@ public void AddOpenTelemetryObservability_WithGrpcProtocol_ThrowsInvalidOperatio var act = () => services.AddOpenTelemetryObservability(config); - act.Should().Throw() - .WithMessage("*grpc*"); + var ex = Assert.Throws(act); + Assert.Contains("grpc", ex.Message, StringComparison.OrdinalIgnoreCase); } [Fact] @@ -82,8 +81,8 @@ public void AddOpenTelemetryObservability_WithInvalidProtocol_ThrowsInvalidOpera var act = () => services.AddOpenTelemetryObservability(config); - act.Should().Throw() - .WithMessage("*unsupported-protocol*"); + var ex = Assert.Throws(act); + Assert.Contains("unsupported-protocol", ex.Message, StringComparison.OrdinalIgnoreCase); } [Fact] @@ -97,7 +96,7 @@ public void AddOpenTelemetryObservability_WithHeader_DoesNotThrow() var act = () => services.AddOpenTelemetryObservability(config); - act.Should().NotThrow(); + Assert.Null(Record.Exception(act)); } [Fact] @@ -111,7 +110,7 @@ public void AddOpenTelemetryObservability_WithNullProtocol_DefaultsToHttpProtobu var act = () => services.AddOpenTelemetryObservability(config); - act.Should().NotThrow(); + Assert.Null(Record.Exception(act)); } [Fact] @@ -125,7 +124,7 @@ public void AddOpenTelemetryObservability_WithEmptyProtocol_DefaultsToHttpProtob var act = () => services.AddOpenTelemetryObservability(config); - act.Should().NotThrow(); + Assert.Null(Record.Exception(act)); } // ── AddApplicationLogging ─────────────────────────────────────────────── @@ -150,7 +149,7 @@ public void AddApplicationLogging_NonDevelopmentEnvironment_ReturnsLoggingBuilde capturedBuilder = builder.AddApplicationLogging(config, env); }); - capturedBuilder.Should().NotBeNull(); + Assert.NotNull(capturedBuilder); } [Fact] @@ -173,7 +172,7 @@ public void AddApplicationLogging_DevelopmentEnvironment_ReturnsLoggingBuilder() capturedBuilder = builder.AddApplicationLogging(config, env); }); - capturedBuilder.Should().NotBeNull(); + Assert.NotNull(capturedBuilder); } [Fact] @@ -191,8 +190,8 @@ public void AddApplicationLogging_WithGrpcProtocol_ThrowsInvalidOperationExcepti var act = () => services.AddLogging(builder => builder.AddApplicationLogging(config, env)); - act.Should().Throw() - .WithMessage("*grpc*"); + var ex = Assert.Throws(act); + Assert.Contains("grpc", ex.Message, StringComparison.OrdinalIgnoreCase); } [Fact] @@ -210,8 +209,8 @@ public void AddApplicationLogging_WithInvalidProtocol_ThrowsInvalidOperationExce var act = () => services.AddLogging(builder => builder.AddApplicationLogging(config, env)); - act.Should().Throw() - .WithMessage("*invalid*"); + var ex = Assert.Throws(act); + Assert.Contains("invalid", ex.Message, StringComparison.OrdinalIgnoreCase); } [Fact] @@ -229,6 +228,6 @@ public void AddApplicationLogging_WithHeader_DoesNotThrow() var act = () => services.AddLogging(builder => builder.AddApplicationLogging(config, env)); - act.Should().NotThrow(); + Assert.Null(Record.Exception(act)); } } diff --git a/TaskFlow.Api.Tests/Extensions/PersistenceServiceExtensionsTests.cs b/TaskFlow.Api.Tests/Extensions/PersistenceServiceExtensionsTests.cs index 7ca0b3e2..84699445 100644 --- a/TaskFlow.Api.Tests/Extensions/PersistenceServiceExtensionsTests.cs +++ b/TaskFlow.Api.Tests/Extensions/PersistenceServiceExtensionsTests.cs @@ -1,4 +1,3 @@ -using FluentAssertions; using Microsoft.Extensions.Logging; using Moq; using TaskFlow.Api.Extensions; @@ -12,7 +11,7 @@ public void EnsureSqliteDirectoryExists_NullConnectionString_DoesNotThrow() { var act = () => PersistenceServiceExtensions.EnsureSqliteDirectoryExists(null); - act.Should().NotThrow(); + Assert.Null(Record.Exception(act)); } [Fact] @@ -20,7 +19,7 @@ public void EnsureSqliteDirectoryExists_EmptyConnectionString_DoesNotThrow() { var act = () => PersistenceServiceExtensions.EnsureSqliteDirectoryExists(string.Empty); - act.Should().NotThrow(); + Assert.Null(Record.Exception(act)); } [Fact] @@ -28,7 +27,7 @@ public void EnsureSqliteDirectoryExists_InMemoryDataSource_DoesNotThrow() { var act = () => PersistenceServiceExtensions.EnsureSqliteDirectoryExists("Data Source=:memory:"); - act.Should().NotThrow(); + Assert.Null(Record.Exception(act)); } [Fact] @@ -36,7 +35,7 @@ public void EnsureSqliteDirectoryExists_FileMemoryDataSource_DoesNotThrow() { var act = () => PersistenceServiceExtensions.EnsureSqliteDirectoryExists("Data Source=file::memory:"); - act.Should().NotThrow(); + Assert.Null(Record.Exception(act)); } [Fact] @@ -47,7 +46,7 @@ public void EnsureSqliteDirectoryExists_ExistingDirectory_DoesNotThrow() var act = () => PersistenceServiceExtensions.EnsureSqliteDirectoryExists(connectionString); - act.Should().NotThrow(); + Assert.Null(Record.Exception(act)); } [Fact] @@ -60,7 +59,7 @@ public void EnsureSqliteDirectoryExists_NonExistentDirectory_CreatesDirectory() { PersistenceServiceExtensions.EnsureSqliteDirectoryExists(connectionString); - Directory.Exists(tempDir).Should().BeTrue(); + Assert.True(Directory.Exists(tempDir)); } finally { diff --git a/TaskFlow.Api.Tests/Extensions/ServiceCollectionExtensionsTests.cs b/TaskFlow.Api.Tests/Extensions/ServiceCollectionExtensionsTests.cs index 7ae475e5..4259d95b 100644 --- a/TaskFlow.Api.Tests/Extensions/ServiceCollectionExtensionsTests.cs +++ b/TaskFlow.Api.Tests/Extensions/ServiceCollectionExtensionsTests.cs @@ -1,4 +1,3 @@ -using FluentAssertions; using FluentValidation; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -32,9 +31,9 @@ public void AddPersistence_ShouldRegisterDbContextAndRepositories() var serviceProvider = services.BuildServiceProvider(); // Assert - serviceProvider.GetService().Should().NotBeNull(); - serviceProvider.GetService().Should().NotBeNull(); - serviceProvider.GetService().Should().BeOfType(); + Assert.NotNull(serviceProvider.GetService()); + Assert.NotNull(serviceProvider.GetService()); + Assert.IsType(serviceProvider.GetService()); } [Fact] @@ -55,8 +54,8 @@ public void AddValidation_ShouldRegisterValidators() var serviceProvider = services.BuildServiceProvider(); // Assert - serviceProvider.GetService>().Should().NotBeNull(); - serviceProvider.GetService>().Should().BeOfType(); + Assert.NotNull(serviceProvider.GetService>()); + Assert.IsType(serviceProvider.GetService>()); } [Fact] @@ -79,7 +78,7 @@ public void AddApplicationHealthChecks_ShouldRegisterHealthChecks() // Assert var healthCheckService = serviceProvider.GetService(); - healthCheckService.Should().NotBeNull(); + Assert.NotNull(healthCheckService); } [Fact] @@ -93,7 +92,7 @@ public void AddOpenApi_ShouldRegisterOpenApiServices() // Assert // Microsoft.AspNetCore.OpenApi registers services with "OpenApi" in the name - services.Should().Contain(s => s.ServiceType.Name.Contains("OpenApi")); + Assert.Contains(services, s => s.ServiceType.Name.Contains("OpenApi")); } [Fact] @@ -106,7 +105,7 @@ public void ConfigureJsonSerialization_ShouldRegisterJsonOptions() services.ConfigureJsonSerialization(); // Assert - services.Should().Contain(s => + Assert.Contains(services, s => s.ServiceType.Name.Contains("JsonOptions") || s.ServiceType.Name.Contains("ConfigureOptions")); } @@ -128,7 +127,7 @@ public void AddOpenTelemetryObservability_ShouldRegisterOpenTelemetryServices() services.AddOpenTelemetryObservability(configuration); // Assert - services.Should().Contain(s => s.ServiceType == typeof(TracerProvider)); + Assert.Contains(services, s => s.ServiceType == typeof(TracerProvider)); } [Fact] @@ -148,7 +147,7 @@ public void AddOpenTelemetryObservability_ShouldReturnServiceCollection() var result = services.AddOpenTelemetryObservability(configuration); // Assert - result.Should().BeSameAs(services); + Assert.Same(services, result); } [Fact] @@ -174,10 +173,10 @@ public void AllExtensions_ShouldWorkTogether() var serviceProvider = services.BuildServiceProvider(); // Assert - verify all critical services are registered - serviceProvider.GetService().Should().NotBeNull(); - serviceProvider.GetService().Should().NotBeNull(); - serviceProvider.GetService>().Should().NotBeNull(); - serviceProvider.GetService().Should().NotBeNull(); + Assert.NotNull(serviceProvider.GetService()); + Assert.NotNull(serviceProvider.GetService()); + Assert.NotNull(serviceProvider.GetService>()); + Assert.NotNull(serviceProvider.GetService()); } [Fact] @@ -204,11 +203,11 @@ public void AllExtensionsWithOpenTelemetry_ShouldRegisterAllServices() services.ConfigureJsonSerialization(); // Assert - verify all services are registered in the collection - services.Should().Contain(s => s.ServiceType == typeof(TaskDbContext)); - services.Should().Contain(s => s.ServiceType == typeof(ITaskRepository)); - services.Should().Contain(s => s.ServiceType == typeof(IValidator)); - services.Should().Contain(s => s.ServiceType == typeof(HealthCheckService)); - services.Should().Contain(s => s.ServiceType == typeof(TracerProvider)); + Assert.Contains(services, s => s.ServiceType == typeof(TaskDbContext)); + Assert.Contains(services, s => s.ServiceType == typeof(ITaskRepository)); + Assert.Contains(services, s => s.ServiceType == typeof(IValidator)); + Assert.Contains(services, s => s.ServiceType == typeof(HealthCheckService)); + Assert.Contains(services, s => s.ServiceType == typeof(TracerProvider)); } [Fact] @@ -224,7 +223,7 @@ public void ConfigureJsonSerialization_OptionLambdas_ExecuteWhenOptionsResolved( .GetRequiredService>().Value; // Assert - httpJsonOptions.SerializerOptions.WriteIndented.Should().BeTrue(); - httpJsonOptions.SerializerOptions.PropertyNamingPolicy.Should().Be(System.Text.Json.JsonNamingPolicy.CamelCase); + Assert.True(httpJsonOptions.SerializerOptions.WriteIndented); + Assert.Equal(System.Text.Json.JsonNamingPolicy.CamelCase, httpJsonOptions.SerializerOptions.PropertyNamingPolicy); } } diff --git a/TaskFlow.Api.Tests/Extensions/UtcDateTimeModelBuilderExtensionsTests.cs b/TaskFlow.Api.Tests/Extensions/UtcDateTimeModelBuilderExtensionsTests.cs index ed337747..0935890c 100644 --- a/TaskFlow.Api.Tests/Extensions/UtcDateTimeModelBuilderExtensionsTests.cs +++ b/TaskFlow.Api.Tests/Extensions/UtcDateTimeModelBuilderExtensionsTests.cs @@ -1,5 +1,4 @@ using System.Text.Json; -using FluentAssertions; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Design.Internal; @@ -64,11 +63,11 @@ public async Task AuditTimestamps_JournalEntry_RoundTripThroughSqlite_HaveUtcKin await using var reader = new TaskDbContext(options); var loaded = await reader.JournalEntries.AsNoTracking().SingleAsync(e => e.Id == id); - loaded.CreatedAt.Kind.Should().Be(DateTimeKind.Utc); - loaded.CreatedAt.Ticks.Should().Be(InstantWithTicks.Ticks); - loaded.UpdatedAt.Should().NotBeNull(); - loaded.UpdatedAt!.Value.Kind.Should().Be(DateTimeKind.Utc); - loaded.UpdatedAt.Value.Ticks.Should().Be(InstantWithTicks.AddMinutes(1).Ticks); + Assert.Equal(DateTimeKind.Utc, loaded.CreatedAt.Kind); + Assert.Equal(InstantWithTicks.Ticks, loaded.CreatedAt.Ticks); + Assert.NotNull(loaded.UpdatedAt); + Assert.Equal(DateTimeKind.Utc, loaded.UpdatedAt!.Value.Kind); + Assert.Equal(InstantWithTicks.AddMinutes(1).Ticks, loaded.UpdatedAt.Value.Ticks); } [Fact] @@ -95,10 +94,10 @@ public async Task AuditTimestamps_JournalLogEntry_RoundTripThroughSqlite_HaveUtc await using var reader = new TaskDbContext(options); var loaded = await reader.JournalLogEntries.AsNoTracking().SingleAsync(e => e.Id == id); - loaded.CreatedAt.Kind.Should().Be(DateTimeKind.Utc); - loaded.CreatedAt.Ticks.Should().Be(InstantWithTicks.Ticks); - loaded.UpdatedAt!.Value.Kind.Should().Be(DateTimeKind.Utc); - loaded.UpdatedAt.Value.Ticks.Should().Be(InstantWithTicks.AddSeconds(5).Ticks); + Assert.Equal(DateTimeKind.Utc, loaded.CreatedAt.Kind); + Assert.Equal(InstantWithTicks.Ticks, loaded.CreatedAt.Ticks); + Assert.Equal(DateTimeKind.Utc, loaded.UpdatedAt!.Value.Kind); + Assert.Equal(InstantWithTicks.AddSeconds(5).Ticks, loaded.UpdatedAt.Value.Ticks); } [Fact] @@ -125,10 +124,10 @@ public async Task AuditTimestamps_JournalNote_RoundTripThroughSqlite_HaveUtcKind await using var reader = new TaskDbContext(options); var loaded = await reader.JournalNotes.AsNoTracking().SingleAsync(e => e.Id == id); - loaded.CreatedAt.Kind.Should().Be(DateTimeKind.Utc); - loaded.CreatedAt.Ticks.Should().Be(InstantWithTicks.Ticks); - loaded.UpdatedAt!.Value.Kind.Should().Be(DateTimeKind.Utc); - loaded.UpdatedAt.Value.Ticks.Should().Be(InstantWithTicks.AddSeconds(5).Ticks); + Assert.Equal(DateTimeKind.Utc, loaded.CreatedAt.Kind); + Assert.Equal(InstantWithTicks.Ticks, loaded.CreatedAt.Ticks); + Assert.Equal(DateTimeKind.Utc, loaded.UpdatedAt!.Value.Kind); + Assert.Equal(InstantWithTicks.AddSeconds(5).Ticks, loaded.UpdatedAt.Value.Ticks); } [Fact] @@ -157,10 +156,10 @@ public async Task AuditTimestamps_Note_RoundTripThroughSqlite_HaveUtcKindAndSame await using var reader = new TaskDbContext(options); var loaded = await reader.Notes.AsNoTracking().SingleAsync(e => e.Id == id); - loaded.CreatedAt.Kind.Should().Be(DateTimeKind.Utc); - loaded.CreatedAt.Ticks.Should().Be(InstantWithTicks.Ticks); - loaded.UpdatedAt!.Value.Kind.Should().Be(DateTimeKind.Utc); - loaded.UpdatedAt.Value.Ticks.Should().Be(InstantWithTicks.AddSeconds(5).Ticks); + Assert.Equal(DateTimeKind.Utc, loaded.CreatedAt.Kind); + Assert.Equal(InstantWithTicks.Ticks, loaded.CreatedAt.Ticks); + Assert.Equal(DateTimeKind.Utc, loaded.UpdatedAt!.Value.Kind); + Assert.Equal(InstantWithTicks.AddSeconds(5).Ticks, loaded.UpdatedAt.Value.Ticks); } [Fact] @@ -185,10 +184,10 @@ public async Task AuditTimestamps_Project_RoundTripThroughSqlite_HaveUtcKindAndS await using var reader = new TaskDbContext(options); var loaded = await reader.Projects.AsNoTracking().SingleAsync(e => e.Id == id); - loaded.CreatedAt.Kind.Should().Be(DateTimeKind.Utc); - loaded.CreatedAt.Ticks.Should().Be(InstantWithTicks.Ticks); - loaded.UpdatedAt!.Value.Kind.Should().Be(DateTimeKind.Utc); - loaded.UpdatedAt.Value.Ticks.Should().Be(InstantWithTicks.AddSeconds(5).Ticks); + Assert.Equal(DateTimeKind.Utc, loaded.CreatedAt.Kind); + Assert.Equal(InstantWithTicks.Ticks, loaded.CreatedAt.Ticks); + Assert.Equal(DateTimeKind.Utc, loaded.UpdatedAt!.Value.Kind); + Assert.Equal(InstantWithTicks.AddSeconds(5).Ticks, loaded.UpdatedAt.Value.Ticks); } [Fact] @@ -216,8 +215,8 @@ public async Task AuditTimestamps_TaskItemEvent_RoundTripThroughSqlite_HaveUtcKi await using var reader = new TaskDbContext(options); var loaded = await reader.TaskItemEvents.AsNoTracking().SingleAsync(e => e.Id == id); - loaded.OccurredAtUtc.Kind.Should().Be(DateTimeKind.Utc); - loaded.OccurredAtUtc.Ticks.Should().Be(InstantWithTicks.Ticks); + Assert.Equal(DateTimeKind.Utc, loaded.OccurredAtUtc.Kind); + Assert.Equal(InstantWithTicks.Ticks, loaded.OccurredAtUtc.Ticks); } [Fact] @@ -237,8 +236,8 @@ public async Task UpdatedAt_Null_RemainsNull_AfterRoundTrip() await using var reader = new TaskDbContext(options); var loaded = await reader.Projects.AsNoTracking().SingleAsync(e => e.Id == id); - loaded.UpdatedAt.Should().BeNull(); - loaded.CreatedAt.Kind.Should().Be(DateTimeKind.Utc); + Assert.Null(loaded.UpdatedAt); + Assert.Equal(DateTimeKind.Utc, loaded.CreatedAt.Kind); } [Fact] @@ -265,8 +264,8 @@ public async Task ApiJson_SerializedAuditTimestamp_EndsWithZ() var json = JsonSerializer.Serialize(dto, JsonSerializerOptionsProvider.Default); using var doc = JsonDocument.Parse(json); - doc.RootElement.GetProperty("createdAt").GetString().Should().EndWith("Z"); - doc.RootElement.GetProperty("updatedAt").GetString().Should().EndWith("Z"); + Assert.EndsWith("Z", doc.RootElement.GetProperty("createdAt").GetString(), StringComparison.Ordinal); + Assert.EndsWith("Z", doc.RootElement.GetProperty("updatedAt").GetString(), StringComparison.Ordinal); } [Fact] @@ -283,7 +282,7 @@ public async Task StoredText_IsUnchanged_ByConverter() command.CommandText = "SELECT CreatedAt FROM JournalEntries"; var stored = (string?)await command.ExecuteScalarAsync(); - stored.Should().Be(InstantWithTicks.ToString("yyyy-MM-dd HH:mm:ss.FFFFFFF")); + Assert.Equal(InstantWithTicks.ToString("yyyy-MM-dd HH:mm:ss.FFFFFFF"), stored); } [Fact] @@ -305,8 +304,8 @@ public async Task RoundTrip_WithoutConverter_WouldBeUnspecified() await using var plain = new PlainContext(plainOptions); var loaded = await plain.Notes.AsNoTracking().SingleAsync(n => n.Id == id); - loaded.CreatedAt.Kind.Should().Be(DateTimeKind.Unspecified); - loaded.CreatedAt.Ticks.Should().Be(InstantWithTicks.Ticks); + Assert.Equal(DateTimeKind.Unspecified, loaded.CreatedAt.Kind); + Assert.Equal(InstantWithTicks.Ticks, loaded.CreatedAt.Ticks); } [Fact] @@ -331,9 +330,9 @@ public async Task TaskItem_DueDate_RoundTrip_IsUnchanged() var a = await reader.TaskItems.AsNoTracking().SingleAsync(t => t.Id == withDueId); var b = await reader.TaskItems.AsNoTracking().SingleAsync(t => t.Id == withoutDueId); - a.DueDate.Should().Be(due); - a.DueDate!.Value.Kind.Should().Be(DateTimeKind.Unspecified); - b.DueDate.Should().BeNull(); + Assert.Equal(due, a.DueDate); + Assert.Equal(DateTimeKind.Unspecified, a.DueDate!.Value.Kind); + Assert.Null(b.DueDate); } [Fact] @@ -356,11 +355,10 @@ public void AllDateTimeProperties_InModel_AreEitherUtcConvertedOrAllowlisted() var name = $"{entityType.ClrType.Name}.{property.Name}"; var isConverted = ReferenceEquals(property.GetValueConverter(), UtcDateTimeModelBuilderExtensions.UtcConverter); - (isConverted || allowlist.Contains(name)) - .Should().BeTrue($"New DateTime property {name}: apply UTC converter or add to allowlist."); + Assert.True(isConverted || allowlist.Contains(name), $"New DateTime property {name}: apply UTC converter or add to allowlist."); if (allowlist.Contains(name)) { - property.GetValueConverter().Should().BeNull(); + Assert.Null(property.GetValueConverter()); } } } @@ -374,15 +372,14 @@ public void ModelSnapshot_HasNoPendingChanges() using var context = new TaskDbContext(options); var snapshotModel = context.GetService().ModelSnapshot?.Model; - snapshotModel.Should().NotBeNull(); + Assert.NotNull(snapshotModel); var finalized = context.GetService() .Initialize(((IMutableModel)snapshotModel!).FinalizeModel()); var differ = context.GetService(); var designModel = context.GetService().Model; - differ.HasDifferences(finalized.GetRelationalModel(), designModel.GetRelationalModel()) - .Should().BeFalse("the UTC converter must not change the schema or model snapshot"); + Assert.False(differ.HasDifferences(finalized.GetRelationalModel(), designModel.GetRelationalModel()), "the UTC converter must not change the schema or model snapshot"); } private sealed class PlainContext(DbContextOptions options) : DbContext(options) diff --git a/TaskFlow.Api.Tests/HealthChecks/HealthCheckLoggingTests.cs b/TaskFlow.Api.Tests/HealthChecks/HealthCheckLoggingTests.cs index 4a485c74..2a3be311 100644 --- a/TaskFlow.Api.Tests/HealthChecks/HealthCheckLoggingTests.cs +++ b/TaskFlow.Api.Tests/HealthChecks/HealthCheckLoggingTests.cs @@ -1,4 +1,3 @@ -using FluentAssertions; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Json; using Microsoft.Extensions.DependencyInjection; @@ -60,13 +59,18 @@ public async Task WriteHealthCheckResponse_ShouldLogUnhealthyStatus() await HealthCheckResponseWriter.WriteHealthCheckResponse(context, healthReport); // Assert - testLogger.Logs.Should().NotBeEmpty("health check failure should be logged"); + // health check failure should be logged + Assert.NotEmpty(testLogger.Logs); var errorLog = testLogger.Logs.FirstOrDefault(e => e.Level == LogLevel.Error); - errorLog.Should().NotBeNull("unhealthy status should be logged at Error level"); - errorLog!.Message.Should().Contain("Health check FAILED", "log should indicate failure"); - errorLog.Message.Should().Contain("/health", "log should include endpoint"); - errorLog.Message.Should().Contain("Unhealthy", "log should include status"); + // unhealthy status should be logged at Error level + Assert.NotNull(errorLog); + // log should indicate failure + Assert.Contains("Health check FAILED", errorLog!.Message); + // log should include endpoint + Assert.Contains("/health", errorLog.Message); + // log should include status + Assert.Contains("Unhealthy", errorLog.Message); } [Fact] @@ -94,13 +98,18 @@ public async Task WriteHealthCheckResponse_ShouldLogDegradedStatus() await HealthCheckResponseWriter.WriteHealthCheckResponse(context, healthReport); // Assert - testLogger.Logs.Should().NotBeEmpty("degraded health check should be logged"); + // degraded health check should be logged + Assert.NotEmpty(testLogger.Logs); var warningLog = testLogger.Logs.FirstOrDefault(e => e.Level == LogLevel.Warning); - warningLog.Should().NotBeNull("degraded status should be logged at Warning level"); - warningLog!.Message.Should().Contain("Health check DEGRADED", "log should indicate degradation"); - warningLog.Message.Should().Contain("/health/ready", "log should include endpoint"); - warningLog.Message.Should().Contain("Degraded", "log should include status"); + // degraded status should be logged at Warning level + Assert.NotNull(warningLog); + // log should indicate degradation + Assert.Contains("Health check DEGRADED", warningLog!.Message); + // log should include endpoint + Assert.Contains("/health/ready", warningLog.Message); + // log should include status + Assert.Contains("Degraded", warningLog.Message); } [Fact] @@ -128,7 +137,8 @@ public async Task WriteHealthCheckResponse_ShouldNotLogHealthyStatus() await HealthCheckResponseWriter.WriteHealthCheckResponse(context, healthReport); // Assert - testLogger.Logs.Should().BeEmpty("healthy status should not generate logs to reduce noise"); + // healthy status should not generate logs to reduce noise + Assert.Empty(testLogger.Logs); } [Fact] @@ -163,9 +173,11 @@ public async Task WriteHealthCheckResponse_ShouldLogMultipleFailedChecks() // Assert var errorLog = testLogger.Logs.FirstOrDefault(e => e.Level == LogLevel.Error); - errorLog.Should().NotBeNull(); - errorLog!.Message.Should().Contain("database", "log should include first failed check"); - errorLog.Message.Should().Contain("external-api", "log should include second failed check"); + Assert.NotNull(errorLog); + // log should include first failed check + Assert.Contains("database", errorLog!.Message); + // log should include second failed check + Assert.Contains("external-api", errorLog.Message); } [Fact] @@ -195,8 +207,9 @@ public async Task WriteHealthCheckResponse_ShouldIncludeExceptionInformation() // Assert var errorLog = testLogger.Logs.FirstOrDefault(e => e.Level == LogLevel.Error); - errorLog.Should().NotBeNull(); - errorLog!.Message.Should().Contain(exceptionMessage, "log should include exception message from health check"); + Assert.NotNull(errorLog); + // log should include exception message from health check + Assert.Contains(exceptionMessage, errorLog!.Message); } /// diff --git a/TaskFlow.Api.Tests/HealthChecks/HealthCheckResponseWriterTests.cs b/TaskFlow.Api.Tests/HealthChecks/HealthCheckResponseWriterTests.cs index d44cf5f2..c8eb6391 100644 --- a/TaskFlow.Api.Tests/HealthChecks/HealthCheckResponseWriterTests.cs +++ b/TaskFlow.Api.Tests/HealthChecks/HealthCheckResponseWriterTests.cs @@ -1,4 +1,3 @@ -using FluentAssertions; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Json; using Microsoft.Extensions.DependencyInjection; @@ -58,10 +57,10 @@ public async Task WriteHealthCheckResponse_ShouldWriteHealthyStatus() using var reader = new StreamReader(responseBody); var response = await reader.ReadToEndAsync(); - response.Should().NotBeNullOrEmpty(); - response.Should().Contain("Healthy"); - response.Should().Contain("database"); - context.Response.ContentType.Should().Be("application/json; charset=utf-8"); + Assert.False(string.IsNullOrEmpty(response)); + Assert.Contains("Healthy", response); + Assert.Contains("database", response); + Assert.Equal("application/json; charset=utf-8", context.Response.ContentType); } [Fact] @@ -92,10 +91,10 @@ public async Task WriteHealthCheckResponse_ShouldWriteUnhealthyStatus() using var reader = new StreamReader(responseBody); var response = await reader.ReadToEndAsync(); - response.Should().NotBeNullOrEmpty(); - response.Should().Contain("Unhealthy"); - response.Should().Contain("database"); - response.Should().Contain("Connection error"); + Assert.False(string.IsNullOrEmpty(response)); + Assert.Contains("Unhealthy", response); + Assert.Contains("database", response); + Assert.Contains("Connection error", response); } [Fact] @@ -132,8 +131,8 @@ public async Task WriteHealthCheckResponse_ShouldIncludeMultipleEntries() using var reader = new StreamReader(responseBody); var response = await reader.ReadToEndAsync(); - response.Should().Contain("database"); - response.Should().Contain("api"); + Assert.Contains("database", response); + Assert.Contains("api", response); } [Fact] @@ -164,7 +163,7 @@ public async Task WriteHealthCheckResponse_ShouldHandleDegradedStatus() using var reader = new StreamReader(responseBody); var response = await reader.ReadToEndAsync(); - response.Should().Contain("Degraded"); + Assert.Contains("Degraded", response); } [Fact] @@ -201,8 +200,8 @@ public async Task WriteHealthCheckResponse_ShouldIncludeCustomData() using var reader = new StreamReader(responseBody); var response = await reader.ReadToEndAsync(); - response.Should().Contain("ConnectionString"); - response.Should().Contain("Version"); + Assert.Contains("ConnectionString", response); + Assert.Contains("Version", response); } [Fact] @@ -236,7 +235,8 @@ public async Task WriteHealthCheckResponse_WhenExceptionMessageContainsSpecialCh var json = await reader.ReadToEndAsync(); var act = () => System.Text.Json.JsonDocument.Parse(json); - act.Should().NotThrow("the response must be valid JSON even when exception messages contain special characters"); - json.Should().Contain("Unhealthy"); + // the response must be valid JSON even when exception messages contain special characters + Assert.Null(Record.Exception(act)); + Assert.Contains("Unhealthy", json); } } diff --git a/TaskFlow.Api.Tests/Helpers/DaysTaggedHelperTests.cs b/TaskFlow.Api.Tests/Helpers/DaysTaggedHelperTests.cs index e4ded8a8..be2ed573 100644 --- a/TaskFlow.Api.Tests/Helpers/DaysTaggedHelperTests.cs +++ b/TaskFlow.Api.Tests/Helpers/DaysTaggedHelperTests.cs @@ -1,4 +1,3 @@ -using FluentAssertions; using TaskFlow.Api.Helpers; namespace TaskFlow.Api.Tests.Helpers; @@ -13,7 +12,7 @@ public void GetDaysTagged_ShouldReturnPositiveCount_WhenBothDatesPresentAndCurre var result = DaysTaggedHelper.GetDaysTagged(firstTaggedDate, currentJournalDate); - result.Should().Be(3); + Assert.Equal(3, result); } [Fact] @@ -24,7 +23,7 @@ public void GetDaysTagged_ShouldReturnZero_WhenFirstTaggedDateIsNull() var result = DaysTaggedHelper.GetDaysTagged(firstTaggedDate, currentJournalDate); - result.Should().Be(0); + Assert.Equal(0, result); } [Fact] @@ -35,7 +34,7 @@ public void GetDaysTagged_ShouldReturnZero_WhenCurrentJournalDateIsNull() var result = DaysTaggedHelper.GetDaysTagged(firstTaggedDate, currentJournalDate); - result.Should().Be(0); + Assert.Equal(0, result); } [Fact] @@ -46,7 +45,7 @@ public void GetDaysTagged_ShouldReturnZero_WhenBothDatesAreNull() var result = DaysTaggedHelper.GetDaysTagged(firstTaggedDate, currentJournalDate); - result.Should().Be(0); + Assert.Equal(0, result); } [Fact] @@ -56,6 +55,6 @@ public void GetDaysTagged_ShouldReturnOne_WhenBothDatesAreEqual() var result = DaysTaggedHelper.GetDaysTagged(date, date); - result.Should().Be(1); + Assert.Equal(1, result); } } diff --git a/TaskFlow.Api.Tests/Helpers/UserDateHelperTests.cs b/TaskFlow.Api.Tests/Helpers/UserDateHelperTests.cs index a0e3529e..ccc5d04b 100644 --- a/TaskFlow.Api.Tests/Helpers/UserDateHelperTests.cs +++ b/TaskFlow.Api.Tests/Helpers/UserDateHelperTests.cs @@ -1,4 +1,3 @@ -using FluentAssertions; using TaskFlow.Api.Helpers; namespace TaskFlow.Api.Tests.Helpers; @@ -10,7 +9,7 @@ public class UserDateHelperTests [Fact] public void GetUserToday_ShouldReturnPreviousDay_WhenOffsetIsBehindUtcAcrossMidnight() { - UserDateHelper.GetUserToday(LateUtc, -300).Should().Be(new DateOnly(2026, 10, 13)); + Assert.Equal(new DateOnly(2026, 10, 13), UserDateHelper.GetUserToday(LateUtc, -300)); } [Fact] @@ -19,33 +18,33 @@ public void GetUserToday_ShouldReturnLocalEveningDate_ForUsCentralDaylightOffset // 02:00 UTC on 10-14 is 21:00 on 10-13 in CDT (-300) var result = UserDateHelper.GetUserToday(LateUtc, -300); - result.Should().Be(new DateOnly(2026, 10, 13)); - result.Should().NotBe(DateOnly.FromDateTime(LateUtc)); + Assert.Equal(new DateOnly(2026, 10, 13), result); + Assert.NotEqual(DateOnly.FromDateTime(LateUtc), result); } [Fact] public void GetUserToday_ShouldReturnUtcDate_WhenOffsetIsZero() { - UserDateHelper.GetUserToday(LateUtc, 0).Should().Be(new DateOnly(2026, 10, 14)); + Assert.Equal(new DateOnly(2026, 10, 14), UserDateHelper.GetUserToday(LateUtc, 0)); } [Fact] public void GetUserToday_ShouldRollForward_WhenOffsetIsPlus840() { var utc = new DateTime(2026, 10, 13, 12, 0, 0, DateTimeKind.Utc); - UserDateHelper.GetUserToday(utc, 840).Should().Be(new DateOnly(2026, 10, 14)); + Assert.Equal(new DateOnly(2026, 10, 14), UserDateHelper.GetUserToday(utc, 840)); } [Fact] public void GetUserToday_ShouldRollBack_WhenOffsetIsMinus720() { var utc = new DateTime(2026, 10, 13, 11, 0, 0, DateTimeKind.Utc); - UserDateHelper.GetUserToday(utc, -720).Should().Be(new DateOnly(2026, 10, 12)); + Assert.Equal(new DateOnly(2026, 10, 12), UserDateHelper.GetUserToday(utc, -720)); } [Fact] public void GetUserToday_ShouldReturnUtcDate_WhenOffsetIsNull() { - UserDateHelper.GetUserToday(LateUtc, null).Should().Be(new DateOnly(2026, 10, 14)); + Assert.Equal(new DateOnly(2026, 10, 14), UserDateHelper.GetUserToday(LateUtc, null)); } } diff --git a/TaskFlow.Api.Tests/Providers/JsonSerializerOptionsProviderTests.cs b/TaskFlow.Api.Tests/Providers/JsonSerializerOptionsProviderTests.cs index a712b2ac..b533957e 100644 --- a/TaskFlow.Api.Tests/Providers/JsonSerializerOptionsProviderTests.cs +++ b/TaskFlow.Api.Tests/Providers/JsonSerializerOptionsProviderTests.cs @@ -1,5 +1,4 @@ using System.Text.Json; -using FluentAssertions; using TaskFlow.Api.Providers; namespace TaskFlow.Api.Tests.Providers; @@ -11,9 +10,9 @@ public void Default_ShouldReturnConfiguredOptions() { var options = JsonSerializerOptionsProvider.Default; - options.Should().NotBeNull(); - options.WriteIndented.Should().BeTrue(); - options.PropertyNamingPolicy.Should().Be(JsonNamingPolicy.CamelCase); + Assert.NotNull(options); + Assert.True(options.WriteIndented); + Assert.Equal(JsonNamingPolicy.CamelCase, options.PropertyNamingPolicy); } [Fact] @@ -22,7 +21,7 @@ public void Default_ShouldBeSameInstance_WhenAccessedMultipleTimes() var first = JsonSerializerOptionsProvider.Default; var second = JsonSerializerOptionsProvider.Default; - first.Should().BeSameAs(second); + Assert.Same(second, first); } [Fact] @@ -30,7 +29,7 @@ public void Default_ShouldBeReadOnly() { var options = JsonSerializerOptionsProvider.Default; - options.IsReadOnly.Should().BeTrue(); + Assert.True(options.IsReadOnly); } [Fact] @@ -40,7 +39,7 @@ public void ConfigureOptions_ShouldSetWriteIndented() JsonSerializerOptionsProvider.ConfigureOptions(options); - options.WriteIndented.Should().BeTrue(); + Assert.True(options.WriteIndented); } [Fact] @@ -50,6 +49,6 @@ public void ConfigureOptions_ShouldSetCamelCaseNamingPolicy() JsonSerializerOptionsProvider.ConfigureOptions(options); - options.PropertyNamingPolicy.Should().Be(JsonNamingPolicy.CamelCase); + Assert.Equal(JsonNamingPolicy.CamelCase, options.PropertyNamingPolicy); } } diff --git a/TaskFlow.Api.Tests/README.md b/TaskFlow.Api.Tests/README.md index eae417af..e33bfb71 100644 --- a/TaskFlow.Api.Tests/README.md +++ b/TaskFlow.Api.Tests/README.md @@ -1,6 +1,6 @@ # TaskFlow.Api.Tests -Comprehensive unit test suite for TaskFlow.Api using xUnit, Moq, and FluentAssertions. +Comprehensive unit test suite for TaskFlow.Api using xUnit (built-in `Assert.*` assertions) and Moq. ## Test Coverage @@ -89,7 +89,7 @@ TaskFlow.Api.Tests/ - **xUnit** - Test framework - **Moq** - Mocking framework -- **FluentAssertions** - Assertion library for better readability +- **xUnit `Assert.*`** - Built-in assertions (no separate assertion library) - **Microsoft.EntityFrameworkCore.InMemory** - In-memory database for testing - **Coverlet** - Code coverage tool @@ -103,7 +103,7 @@ All tests follow industry best practices: 4. **Edge case coverage** - null values, empty collections, boundary conditions 5. **Mocking external dependencies** - services, repositories, validators 6. **In-memory database** for repository tests (realistic without external dependencies) -7. **Clear assertions** using FluentAssertions for readability +7. **Clear assertions** using xUnit `Assert.*` ## CI Integration diff --git a/TaskFlow.Api.Tests/Repositories/JournalEntryRepositoryTests.cs b/TaskFlow.Api.Tests/Repositories/JournalEntryRepositoryTests.cs index 66fb6d0e..ae3e63ca 100644 --- a/TaskFlow.Api.Tests/Repositories/JournalEntryRepositoryTests.cs +++ b/TaskFlow.Api.Tests/Repositories/JournalEntryRepositoryTests.cs @@ -1,4 +1,3 @@ -using FluentAssertions; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Diagnostics; @@ -41,9 +40,9 @@ public async Task AddAndGetByDate_ShouldPersistEntry() var found = await repo.GetByDateAsync(date); - found.Should().NotBeNull(); - found!.Title.Should().Be("May 10"); - found.CreatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5)); + Assert.NotNull(found); + Assert.Equal("May 10", found!.Title); + Assert.Equal(DateTime.UtcNow, found.CreatedAt, TimeSpan.FromSeconds(5)); } [Fact] @@ -58,8 +57,8 @@ public async Task GetAll_ShouldReturnDescendingByDate() var all = (await repo.GetAllAsync()).ToList(); - all.Should().HaveCount(2); - all[0].Title.Should().Be("Newer"); + Assert.Equal(2, all.Count()); + Assert.Equal("Newer", all[0].Title); } [Fact] @@ -75,13 +74,13 @@ public async Task AddTodoAndRemoveTodo_ShouldManageLinks() await repo.AddTodoAsync(entry.Id, task.Id); - (await repo.TodoExistsAsync(entry.Id, task.Id)).Should().BeTrue(); - (await repo.GetTodosAsync(entry.Id)).Should().ContainSingle(t => t.Id == task.Id); + Assert.True((await repo.TodoExistsAsync(entry.Id, task.Id))); + Assert.Single((await repo.GetTodosAsync(entry.Id)), t => t.Id == task.Id); await repo.RemoveTodoAsync(entry.Id, task.Id); - (await repo.TodoExistsAsync(entry.Id, task.Id)).Should().BeFalse(); - task.CurrentJournalEntryId.Should().BeNull(); + Assert.False((await repo.TodoExistsAsync(entry.Id, task.Id))); + Assert.Null(task.CurrentJournalEntryId); } [Fact] @@ -97,10 +96,10 @@ public async Task AddTodoAsync_ShouldStampOwnershipMetadata_OnFirstAssignment() var result = await repo.AddTodoAsync(entry.Id, task.Id); - result.Should().Be(AddTodoResult.Success); - task.CurrentJournalEntryId.Should().Be(entry.Id); - task.FirstTaggedDate.Should().Be(entry.Date); - task.MoveCount.Should().Be(0); + Assert.Equal(AddTodoResult.Success, result); + Assert.Equal(entry.Id, task.CurrentJournalEntryId); + Assert.Equal(entry.Date, task.FirstTaggedDate); + Assert.Equal(0, task.MoveCount); } [Fact] @@ -119,12 +118,12 @@ public async Task AddTodoAsync_ShouldMoveTaskBetweenEntries_AndIncrementMoveCoun await repo.AddTodoAsync(firstEntry.Id, task.Id); var moveResult = await repo.AddTodoAsync(secondEntry.Id, task.Id); - moveResult.Should().Be(AddTodoResult.Success); - task.CurrentJournalEntryId.Should().Be(secondEntry.Id); - task.FirstTaggedDate.Should().Be(firstEntry.Date); - task.MoveCount.Should().Be(1); - (await repo.TodoExistsAsync(firstEntry.Id, task.Id)).Should().BeFalse(); - (await repo.TodoExistsAsync(secondEntry.Id, task.Id)).Should().BeTrue(); + Assert.Equal(AddTodoResult.Success, moveResult); + Assert.Equal(secondEntry.Id, task.CurrentJournalEntryId); + Assert.Equal(firstEntry.Date, task.FirstTaggedDate); + Assert.Equal(1, task.MoveCount); + Assert.False((await repo.TodoExistsAsync(firstEntry.Id, task.Id))); + Assert.True((await repo.TodoExistsAsync(secondEntry.Id, task.Id))); } [Fact] @@ -141,7 +140,7 @@ public async Task AddTodoAsync_ShouldReturnAlreadyLinked_WhenLinkExists() await repo.AddTodoAsync(entry.Id, task.Id); var result = await repo.AddTodoAsync(entry.Id, task.Id); - result.Should().Be(AddTodoResult.AlreadyLinked); + Assert.Equal(AddTodoResult.AlreadyLinked, result); } [Fact] @@ -152,7 +151,7 @@ public async Task AddTodoAsync_ShouldReturnEntryNotFound_WhenEntryMissing() var result = await repo.AddTodoAsync(999, 1); - result.Should().Be(AddTodoResult.EntryNotFound); + Assert.Equal(AddTodoResult.EntryNotFound, result); } [Fact] @@ -164,7 +163,7 @@ public async Task AddTodoAsync_ShouldReturnTaskNotFound_WhenTaskMissing() var entry = await repo.AddAsync(new JournalEntry { Title = "May 10", Date = DateOnly.FromDateTime(DateTime.UtcNow.AddDays(1)) }); var result = await repo.AddTodoAsync(entry.Id, 999); - result.Should().Be(AddTodoResult.TaskNotFound); + Assert.Equal(AddTodoResult.TaskNotFound, result); } [Fact] @@ -180,7 +179,7 @@ public async Task AddAsync_ShouldThrowDuplicateJournalDateException_WhenDateAlre var act = async () => await repo.AddAsync(new JournalEntry { Title = "Duplicate", Date = date }); - await act.Should().ThrowAsync(); + await Assert.ThrowsAsync(act); } [Fact] @@ -194,7 +193,7 @@ public async Task Delete_ShouldRemoveEntry_AndIgnoreMissing() await repo.DeleteAsync(entry.Id); await repo.DeleteAsync(999); - (await repo.GetByIdAsync(entry.Id)).Should().BeNull(); + Assert.Null((await repo.GetByIdAsync(entry.Id))); } [Fact] @@ -209,12 +208,12 @@ public async Task AddAsync_ShouldNotMoveAnyTasks_WhenCreatingFutureOrPastEntry() var future = await repo.AddAsync(new JournalEntry { Title = "Future", Date = today.AddDays(1) }); var past = await repo.AddAsync(new JournalEntry { Title = "Past", Date = today.AddDays(-5) }); - (await repo.GetTodosAsync(source.Id)).Should().ContainSingle(t => t.Id == task.Id); - (await repo.GetTodosAsync(future.Id)).Should().BeEmpty(); - (await repo.GetTodosAsync(past.Id)).Should().BeEmpty(); - task.CurrentJournalEntryId.Should().Be(source.Id); - task.MoveCount.Should().Be(0); - context.TaskItemEvents.Should().BeEmpty(); + Assert.Single((await repo.GetTodosAsync(source.Id)), t => t.Id == task.Id); + Assert.Empty((await repo.GetTodosAsync(future.Id))); + Assert.Empty((await repo.GetTodosAsync(past.Id))); + Assert.Equal(source.Id, task.CurrentJournalEntryId); + Assert.Equal(0, task.MoveCount); + Assert.Empty(context.TaskItemEvents); } [Fact] @@ -229,11 +228,11 @@ public async Task CarryForwardIncompleteTasksAsync_ShouldLeaveCompletedTasksInPl var moved = await repo.CarryForwardIncompleteTasksAsync(target.Id, today); - moved.Should().Equal(open.Id); - (await repo.GetTodosAsync(target.Id)).Should().ContainSingle(t => t.Id == open.Id); - (await repo.GetTodosAsync(source.Id)).Should().ContainSingle(t => t.Id == done.Id); - done.CurrentJournalEntryId.Should().Be(source.Id); - done.MoveCount.Should().Be(0); + Assert.Equal(new[] { open.Id }, moved); + Assert.Single((await repo.GetTodosAsync(target.Id)), t => t.Id == open.Id); + Assert.Single((await repo.GetTodosAsync(source.Id)), t => t.Id == done.Id); + Assert.Equal(source.Id, done.CurrentJournalEntryId); + Assert.Equal(0, done.MoveCount); } [Fact] @@ -248,12 +247,12 @@ public async Task CarryForwardIncompleteTasksAsync_ShouldMoveTasksFromAllEarlier var moved = await repo.CarryForwardIncompleteTasksAsync(target.Id, today); - moved.Should().Equal(olderTask.Id, newerTask.Id); - (await repo.GetTodosAsync(target.Id)).Select(t => t.Id).Should().BeEquivalentTo([olderTask.Id, newerTask.Id]); - (await repo.GetTodosAsync(older.Id)).Should().BeEmpty(); - (await repo.GetTodosAsync(newer.Id)).Should().BeEmpty(); - olderTask.FirstTaggedDate.Should().Be(older.Date); - newerTask.FirstTaggedDate.Should().Be(newer.Date); + Assert.Equal(new[] { olderTask.Id, newerTask.Id }, moved); + Assert.Equal(new[] { olderTask.Id, newerTask.Id }.Order(), (await repo.GetTodosAsync(target.Id)).Select(t => t.Id).Order()); + Assert.Empty((await repo.GetTodosAsync(older.Id))); + Assert.Empty((await repo.GetTodosAsync(newer.Id))); + Assert.Equal(older.Date, olderTask.FirstTaggedDate); + Assert.Equal(newer.Date, newerTask.FirstTaggedDate); } [Fact] @@ -275,11 +274,11 @@ public async Task AddTodoAsync_AndRemoveTodoAsync_ShouldWriteTaskHistoryEvents() .OrderBy(e => e.OccurredAtUtc) .ToListAsync(); - events.Should().HaveCount(2); - events[0].EventType.Should().Be("AssignedToJournalDay"); - events[0].ToJournalEntryId.Should().Be(entry.Id); - events[1].EventType.Should().Be("RemovedFromJournalDay"); - events[1].FromJournalEntryId.Should().Be(entry.Id); + Assert.Equal(2, events.Count()); + Assert.Equal("AssignedToJournalDay", events[0].EventType); + Assert.Equal(entry.Id, events[0].ToJournalEntryId); + Assert.Equal("RemovedFromJournalDay", events[1].EventType); + Assert.Equal(entry.Id, events[1].FromJournalEntryId); } // Verifies the GetAllAsync include chain specifically — the controller path uses GetTodosAsync, @@ -305,7 +304,7 @@ public async Task GetAllAsync_ShouldIncludeChildTaskItems_OnTodos() var foundEntry = all.First(e => e.Id == entry.Id); var parentTodo = foundEntry.Todos.First(t => t.Id == parent.Id); - parentTodo.ChildTaskItems.Should().ContainSingle(c => c.Id == child.Id); + Assert.Single(parentTodo.ChildTaskItems, c => c.Id == child.Id); } [Fact] @@ -331,7 +330,7 @@ public async Task GetTodosAsync_ShouldIncludeChildTaskItems_WhenParentIsInEntry( var todos = (await repo.GetTodosAsync(entry.Id)).ToList(); var parentTodo = todos.First(t => t.Id == parent.Id); - parentTodo.ChildTaskItems.Should().ContainSingle(c => c.Id == child.Id); + Assert.Single(parentTodo.ChildTaskItems, c => c.Id == child.Id); } [Fact] @@ -353,9 +352,9 @@ public async Task GetByIdAsync_ShouldIncludeChildTaskItems_OnTodos() var found = await repo.GetByIdAsync(entry.Id); - found.Should().NotBeNull(); + Assert.NotNull(found); var parentTodo = found!.Todos.First(t => t.Id == parent.Id); - parentTodo.ChildTaskItems.Should().ContainSingle(c => c.Id == child.Id); + Assert.Single(parentTodo.ChildTaskItems, c => c.Id == child.Id); } [Fact] @@ -378,9 +377,9 @@ public async Task GetByDateAsync_ShouldIncludeChildTaskItems_OnTodos() var found = await repo.GetByDateAsync(date); - found.Should().NotBeNull(); + Assert.NotNull(found); var parentTodo = found!.Todos.First(t => t.Id == parent.Id); - parentTodo.ChildTaskItems.Should().ContainSingle(c => c.Id == child.Id); + Assert.Single(parentTodo.ChildTaskItems, c => c.Id == child.Id); } [Fact] @@ -439,17 +438,17 @@ public async Task GetTodosAsync_ShouldNestChild_WhenBothParentAndChildLinkedToSa // Without AsNoTracking, the identity map collision causes the child's ParentTaskItemId // to read as null, which makes it falsely appear as a root item in the controller filter. var rootItems = todos.Where(t => t.ParentTaskItemId is null).ToList(); - rootItems.Should().ContainSingle(t => t.Id == parentId); + Assert.Single(rootItems, t => t.Id == parentId); // Assert 2: the parent has exactly one child with the expected Id. var parentTodo = todos.First(t => t.Id == parentId); - parentTodo.ChildTaskItems.Should().ContainSingle(c => c.Id == childId); + Assert.Single(parentTodo.ChildTaskItems, c => c.Id == childId); // Assert 3: the child's ParentTaskItemId is not null and correctly points to the parent. // This is the key assertion: without AsNoTracking this would be null due to identity map collision. var childItem = parentTodo.ChildTaskItems.First(c => c.Id == childId); - childItem.ParentTaskItemId.Should().NotBeNull(); - childItem.ParentTaskItemId.Should().Be(parentId); + Assert.NotNull(childItem.ParentTaskItemId); + Assert.Equal(parentId, childItem.ParentTaskItemId); } [Fact] @@ -485,8 +484,8 @@ public async Task GetTodosAsync_ShouldIncludeCurrentJournalEntry_OnChildTaskItem var parentTodo = todos.First(t => t.Id == parent.Id); var childItem = parentTodo.ChildTaskItems.First(c => c.Id == child.Id); - childItem.CurrentJournalEntry.Should().NotBeNull(); - childItem.CurrentJournalEntry!.Id.Should().Be(secondEntry.Id); + Assert.NotNull(childItem.CurrentJournalEntry); + Assert.Equal(secondEntry.Id, childItem.CurrentJournalEntry!.Id); } [Fact] @@ -504,21 +503,21 @@ public async Task CarryForwardIncompleteTasksAsync_ShouldSetCountersAndEvents_Fo var moved = await repo.CarryForwardIncompleteTasksAsync(target.Id, today); - moved.Should().Equal(taskA.Id, taskB.Id); - taskA.CurrentJournalEntryId.Should().Be(target.Id); - taskA.MoveCount.Should().Be(1); - taskA.FirstTaggedDate.Should().Be(a.Date); - taskB.MoveCount.Should().Be(3); - taskB.FirstTaggedDate.Should().Be(today.AddDays(-9)); + Assert.Equal(new[] { taskA.Id, taskB.Id }, moved); + Assert.Equal(target.Id, taskA.CurrentJournalEntryId); + Assert.Equal(1, taskA.MoveCount); + Assert.Equal(a.Date, taskA.FirstTaggedDate); + Assert.Equal(3, taskB.MoveCount); + Assert.Equal(today.AddDays(-9), taskB.FirstTaggedDate); var events = context.TaskItemEvents.ToList(); - events.Should().HaveCount(2); - events.Should().OnlyContain(e => e.EventType == "ReassignedToJournalDay" + Assert.Equal(2, events.Count()); + Assert.All(events, e => Assert.True(e.EventType == "ReassignedToJournalDay" && e.ChangeSummary == "Automatically moved forward to the next journal day." - && e.ToJournalEntryId == target.Id && e.ToJournalDate == today); - events.Single(e => e.TaskItemId == taskA.Id).FromJournalEntryId.Should().Be(a.Id); - events.Single(e => e.TaskItemId == taskA.Id).FromJournalDate.Should().Be(a.Date); - events.Single(e => e.TaskItemId == taskB.Id).FromJournalEntryId.Should().Be(b.Id); - events.Single(e => e.TaskItemId == taskB.Id).FromJournalDate.Should().Be(b.Date); + && e.ToJournalEntryId == target.Id && e.ToJournalDate == today)); + Assert.Equal(a.Id, events.Single(e => e.TaskItemId == taskA.Id).FromJournalEntryId); + Assert.Equal(a.Date, events.Single(e => e.TaskItemId == taskA.Id).FromJournalDate); + Assert.Equal(b.Id, events.Single(e => e.TaskItemId == taskB.Id).FromJournalEntryId); + Assert.Equal(b.Date, events.Single(e => e.TaskItemId == taskB.Id).FromJournalDate); } [Fact] @@ -534,9 +533,9 @@ public async Task CarryForwardIncompleteTasksAsync_ShouldRespectStatusAndIsCompl var moved = await repo.CarryForwardIncompleteTasksAsync(target.Id, today); - moved.Should().Equal(openTask.Id); - statusOnly.CurrentJournalEntryId.Should().Be(source.Id); - flagOnly.CurrentJournalEntryId.Should().Be(source.Id); + Assert.Equal(new[] { openTask.Id }, moved); + Assert.Equal(source.Id, statusOnly.CurrentJournalEntryId); + Assert.Equal(source.Id, flagOnly.CurrentJournalEntryId); } [Fact] @@ -554,12 +553,12 @@ public async Task CarryForwardIncompleteTasksAsync_ShouldNotTouchPlannedDayBetwe var moved = await repo.CarryForwardIncompleteTasksAsync(target.Id, today); - moved.Should().Equal(taskA.Id); - taskA.CurrentJournalEntryId.Should().Be(target.Id); - (await repo.GetTodosAsync(a.Id)).Should().BeEmpty(); - taskFuture.MoveCount.Should().Be(0); - taskFuture.CurrentJournalEntryId.Should().NotBe(target.Id); - orphan.CurrentJournalEntryId.Should().BeNull(); + Assert.Equal(new[] { taskA.Id }, moved); + Assert.Equal(target.Id, taskA.CurrentJournalEntryId); + Assert.Empty((await repo.GetTodosAsync(a.Id))); + Assert.Equal(0, taskFuture.MoveCount); + Assert.NotEqual(target.Id, taskFuture.CurrentJournalEntryId); + Assert.Null(orphan.CurrentJournalEntryId); } [Fact] @@ -574,9 +573,9 @@ public async Task CarryForwardIncompleteTasksAsync_ShouldCarryIntoPreExistingTod var moved = await repo.CarryForwardIncompleteTasksAsync(target.Id, today); - moved.Should().Equal(old.Id); - (await repo.GetTodosAsync(target.Id)).Select(t => t.Id).Should().BeEquivalentTo([existing.Id, old.Id]); - existing.MoveCount.Should().Be(0); + Assert.Equal(new[] { old.Id }, moved); + Assert.Equal(new[] { existing.Id, old.Id }.Order(), (await repo.GetTodosAsync(target.Id)).Select(t => t.Id).Order()); + Assert.Equal(0, existing.MoveCount); } [Fact] @@ -588,11 +587,11 @@ public async Task CarryForwardIncompleteTasksAsync_ShouldBeIdempotent_WhenRunTwi var (_, task) = await SeedLinkedTaskAsync(context, today.AddDays(-1)); var target = await repo.AddAsync(new JournalEntry { Title = "Today", Date = today }); - (await repo.CarryForwardIncompleteTasksAsync(target.Id, today)).Should().Equal(task.Id); - (await repo.CarryForwardIncompleteTasksAsync(target.Id, today)).Should().BeEmpty(); + Assert.Equal(new[] { task.Id }, (await repo.CarryForwardIncompleteTasksAsync(target.Id, today))); + Assert.Empty((await repo.CarryForwardIncompleteTasksAsync(target.Id, today))); - task.MoveCount.Should().Be(1); - context.TaskItemEvents.Should().HaveCount(1); + Assert.Equal(1, task.MoveCount); + Assert.Single(context.TaskItemEvents); } [Fact] @@ -611,11 +610,11 @@ public async Task CarryForwardIncompleteTasksAsync_ShouldNotPullBackTaskComplete await context.SaveChangesAsync(); await repo.RemoveTodoAsync(target.Id, removedLater.Id); - (await repo.CarryForwardIncompleteTasksAsync(target.Id, today)).Should().BeEmpty(); - completedLater.CurrentJournalEntryId.Should().Be(target.Id); - removedLater.CurrentJournalEntryId.Should().BeNull(); - completedLater.MoveCount.Should().Be(1); - removedLater.MoveCount.Should().Be(1); + Assert.Empty((await repo.CarryForwardIncompleteTasksAsync(target.Id, today))); + Assert.Equal(target.Id, completedLater.CurrentJournalEntryId); + Assert.Null(removedLater.CurrentJournalEntryId); + Assert.Equal(1, completedLater.MoveCount); + Assert.Equal(1, removedLater.MoveCount); } [Fact] @@ -632,9 +631,9 @@ public async Task CarryForwardIncompleteTasksAsync_ShouldMoveSubTasksIndependent await repo.CarryForwardIncompleteTasksAsync(target.Id, today); - openChild.CurrentJournalEntryId.Should().Be(target.Id); - doneChild.CurrentJournalEntryId.Should().Be(source.Id); - doneParent.CurrentJournalEntryId.Should().Be(source.Id); + Assert.Equal(target.Id, openChild.CurrentJournalEntryId); + Assert.Equal(source.Id, doneChild.CurrentJournalEntryId); + Assert.Equal(source.Id, doneParent.CurrentJournalEntryId); } [Fact] @@ -646,10 +645,10 @@ public async Task CarryForwardIncompleteTasksAsync_ShouldReturnZero_WhenTargetIs var (_, task) = await SeedLinkedTaskAsync(context, today.AddDays(-1)); var tomorrow = await repo.AddAsync(new JournalEntry { Title = "Tomorrow", Date = today.AddDays(1) }); - (await repo.CarryForwardIncompleteTasksAsync(tomorrow.Id, today)).Should().BeEmpty(); + Assert.Empty((await repo.CarryForwardIncompleteTasksAsync(tomorrow.Id, today))); - task.MoveCount.Should().Be(0); - context.TaskItemEvents.Should().BeEmpty(); + Assert.Equal(0, task.MoveCount); + Assert.Empty(context.TaskItemEvents); } [Fact] @@ -658,7 +657,7 @@ public async Task CarryForwardIncompleteTasksAsync_ShouldReturnZero_WhenEntryMis using var context = CreateInMemoryContext(); var repo = new JournalEntryRepository(context); - (await repo.CarryForwardIncompleteTasksAsync(999, new DateOnly(2026, 10, 13))).Should().BeEmpty(); + Assert.Empty((await repo.CarryForwardIncompleteTasksAsync(999, new DateOnly(2026, 10, 13)))); } [Fact] @@ -673,8 +672,8 @@ public async Task CarryForwardIncompleteTasksAsync_ShouldReturnEmpty_WhenNoIncom await context.SaveChangesAsync(); var target = await repo.AddAsync(new JournalEntry { Title = "Today", Date = today }); - (await repo.CarryForwardIncompleteTasksAsync(target.Id, today)).Should().BeEmpty(); - context.TaskItemEvents.Should().BeEmpty(); + Assert.Empty((await repo.CarryForwardIncompleteTasksAsync(target.Id, today))); + Assert.Empty(context.TaskItemEvents); } [Fact] @@ -698,9 +697,9 @@ public async Task CarryForwardIncompleteTasksAsync_ShouldReturnAllIdsInSourceDat var moved = await repo.CarryForwardIncompleteTasksAsync(target.Id, today); - moved.Should().HaveCount(50); - moved.Should().Equal(expectedOlder.Order().Concat(expectedNewer.Order())); - context.TaskItemEvents.Count(e => e.EventType == "ReassignedToJournalDay").Should().Be(moved.Count); + Assert.Equal(50, moved.Count()); + Assert.Equal(expectedOlder.Order().Concat(expectedNewer.Order()), moved); + Assert.Equal(moved.Count, context.TaskItemEvents.Count(e => e.EventType == "ReassignedToJournalDay")); } [Fact] @@ -731,15 +730,15 @@ public async Task CarryForwardIncompleteTasksAsync_ShouldReturnZeroAndRollBack_W await using (var loser = new TaskDbContext(options)) { var moved = await new JournalEntryRepository(loser).CarryForwardIncompleteTasksAsync(targetId, today); - moved.Should().BeEmpty(); + Assert.Empty(moved); } - interceptor.Fired.Should().BeTrue(); + Assert.True(interceptor.Fired); await using var verify = new TaskDbContext(seedOptions); var reloaded = await verify.TaskItems.SingleAsync(t => t.Id == taskId); - reloaded.MoveCount.Should().Be(0); - reloaded.CurrentJournalEntryId.Should().Be(sourceId); - (await verify.TaskItemEvents.CountAsync()).Should().Be(0); + Assert.Equal(0, reloaded.MoveCount); + Assert.Equal(sourceId, reloaded.CurrentJournalEntryId); + Assert.Equal(0, (await verify.TaskItemEvents.CountAsync())); } private sealed class InsertJoinRowInterceptor(int entryId, int taskId) : SaveChangesInterceptor diff --git a/TaskFlow.Api.Tests/Repositories/JournalEntryRepositoryUpdateTests.cs b/TaskFlow.Api.Tests/Repositories/JournalEntryRepositoryUpdateTests.cs index f5420eb9..84caf48f 100644 --- a/TaskFlow.Api.Tests/Repositories/JournalEntryRepositoryUpdateTests.cs +++ b/TaskFlow.Api.Tests/Repositories/JournalEntryRepositoryUpdateTests.cs @@ -1,4 +1,3 @@ -using FluentAssertions; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using TaskFlow.Api.Data; @@ -54,18 +53,20 @@ public async Task UpdateAsync_ShouldPersistTitleAndSummary_WhenTheEntryHasPlanne using var context = NewContext(connection); var repo = new JournalEntryRepository(context); var loaded = (await repo.GetByIdAsync(entryId))!; - loaded.Todos.Should().HaveCount(2, "the scenario needs planned tasks for the bug to occur"); + // the scenario needs planned tasks for the bug to occur + Assert.Equal(2, loaded.Todos.Count()); loaded.Title = "Renamed"; loaded.Summary = "new summary"; var act = () => repo.UpdateAsync(loaded); - await act.Should().NotThrowAsync(); + Assert.Null(await Record.ExceptionAsync(act)); using var verify = NewContext(connection); var saved = await verify.JournalEntries.AsNoTracking().SingleAsync(); - saved.Title.Should().Be("Renamed"); - saved.Summary.Should().Be("new summary"); - saved.UpdatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5)); + Assert.Equal("Renamed", saved.Title); + Assert.Equal("new summary", saved.Summary); + Assert.NotNull(saved.UpdatedAt); + Assert.Equal(DateTime.UtcNow, saved.UpdatedAt.Value, TimeSpan.FromSeconds(5)); } } @@ -99,10 +100,10 @@ public async Task UpdateAsync_ShouldPersistSummary_WhenTheEntryHasASinglePlanned loaded.Summary = "works"; var act = () => repo.UpdateAsync(loaded); - await act.Should().NotThrowAsync(); + Assert.Null(await Record.ExceptionAsync(act)); using var verify = NewContext(connection); - (await verify.JournalEntries.AsNoTracking().SingleAsync()).Summary.Should().Be("works"); - (await verify.JournalEntries.Include(e => e.Todos).AsNoTracking().SingleAsync()).Todos.Should().ContainSingle(); + Assert.Equal("works", (await verify.JournalEntries.AsNoTracking().SingleAsync()).Summary); + Assert.Single((await verify.JournalEntries.Include(e => e.Todos).AsNoTracking().SingleAsync()).Todos); } } @@ -124,12 +125,12 @@ public async Task UpdateAsync_ShouldLeaveTodosLogsAndLinkedTasksUntouched() await repo.UpdateAsync(loaded); using var verify = NewContext(connection); - (await verify.TaskItems.AsNoTracking().SingleAsync(t => t.Id == taskId)).Title.Should().Be("Linked task"); - (await verify.TaskItems.AsNoTracking().SingleAsync(t => t.Id == childId)).Priority.Should().Be(Priority.Low); - (await verify.JournalLogEntries.AsNoTracking().SingleAsync()).Content.Should().Be("a log line"); + Assert.Equal("Linked task", (await verify.TaskItems.AsNoTracking().SingleAsync(t => t.Id == taskId)).Title); + Assert.Equal(Priority.Low, (await verify.TaskItems.AsNoTracking().SingleAsync(t => t.Id == childId)).Priority); + Assert.Equal("a log line", (await verify.JournalLogEntries.AsNoTracking().SingleAsync()).Content); var reloaded = await new JournalEntryRepository(verify).GetByIdAsync(entryId); - reloaded!.Todos.Select(t => t.Id).Should().BeEquivalentTo([taskId, childId]); + Assert.Equal(new[] { taskId, childId }.Order(), reloaded!.Todos.Select(t => t.Id).Order()); } } @@ -150,8 +151,8 @@ public async Task UpdateAsync_ShouldNotChangeDateOrCreatedAt() using var verify = NewContext(connection); var saved = await verify.JournalEntries.AsNoTracking().SingleAsync(); - saved.Date.Should().Be(Day); - saved.CreatedAt.Should().Be(new DateTime(2026, 10, 9, 8, 0, 0, DateTimeKind.Utc)); + Assert.Equal(Day, saved.Date); + Assert.Equal(new DateTime(2026, 10, 9, 8, 0, 0, DateTimeKind.Utc), saved.CreatedAt); } } @@ -167,7 +168,9 @@ public async Task UpdateAsync_ShouldSetUpdatedAtOnTheInstancePassedIn() await repo.UpdateAsync(loaded); - loaded.UpdatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5), "the controller returns this instance"); + Assert.NotNull(loaded.UpdatedAt); + // the controller returns this instance + Assert.Equal(DateTime.UtcNow, loaded.UpdatedAt.Value, TimeSpan.FromSeconds(5)); } } @@ -182,7 +185,7 @@ public async Task UpdateAsync_ShouldWork_ForAnEntryTrackedByTheSameContext() entry.Summary = "tracked edit"; await repo.UpdateAsync(entry); - (await context.JournalEntries.AsNoTracking().SingleAsync()).Summary.Should().Be("tracked edit"); + Assert.Equal("tracked edit", (await context.JournalEntries.AsNoTracking().SingleAsync()).Summary); } [Fact] @@ -194,6 +197,7 @@ public async Task UpdateAsync_ShouldThrow_WhenTheEntryDoesNotExist() var act = () => repo.UpdateAsync(new JournalEntry { Id = 404, Title = "ghost", Date = Day }); - await act.Should().ThrowAsync().WithMessage("*404*"); + var ex = await Assert.ThrowsAsync(act); + Assert.Contains("404", ex.Message, StringComparison.OrdinalIgnoreCase); } } diff --git a/TaskFlow.Api.Tests/Repositories/JournalLogEntryRepositoryTests.cs b/TaskFlow.Api.Tests/Repositories/JournalLogEntryRepositoryTests.cs index 27de8263..4140241f 100644 --- a/TaskFlow.Api.Tests/Repositories/JournalLogEntryRepositoryTests.cs +++ b/TaskFlow.Api.Tests/Repositories/JournalLogEntryRepositoryTests.cs @@ -1,4 +1,3 @@ -using FluentAssertions; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using TaskFlow.Api.Data; @@ -44,9 +43,9 @@ public async Task AddAndGetAll_ShouldReturnLogsOrdered() var logs = (await repo.GetAllByEntryIdAsync(entry.Id)).ToList(); - logs.Should().HaveCount(2); - logs[0].Content.Should().Be("A"); - logs[1].Content.Should().Be("B"); + Assert.Equal(2, logs.Count()); + Assert.Equal("A", logs[0].Content); + Assert.Equal("B", logs[1].Content); } [Fact] @@ -64,13 +63,13 @@ public async Task UpdateAndDelete_ShouldSetUpdatedAtAndRemove() await repo.UpdateAsync(log); var fetched = await repo.GetByIdAsync(entry.Id, log.Id); - fetched.Should().NotBeNull(); - fetched!.UpdatedAt.Should().NotBeNull(); + Assert.NotNull(fetched); + Assert.NotNull(fetched!.UpdatedAt); await repo.DeleteAsync(entry.Id, log.Id); await repo.DeleteAsync(entry.Id, 999); - (await repo.GetByIdAsync(entry.Id, log.Id)).Should().BeNull(); + Assert.Null((await repo.GetByIdAsync(entry.Id, log.Id))); } [Fact] @@ -97,9 +96,9 @@ public async Task GetByIdAsync_ShouldIncludeTaskItem() var fetched = await repo.GetByIdAsync(entry.Id, log.Id); - fetched.Should().NotBeNull(); - fetched!.TaskItem.Should().NotBeNull(); - fetched.TaskItem!.Title.Should().Be("Linked Task"); + Assert.NotNull(fetched); + Assert.NotNull(fetched!.TaskItem); + Assert.Equal("Linked Task", fetched.TaskItem!.Title); } } @@ -131,9 +130,9 @@ public async Task DeleteTask_ShouldNullTaskItemId_AndPreserveSnapshot() var fetched = await repo.GetByIdAsync(entry.Id, log.Id); - fetched.Should().NotBeNull(); - fetched!.TaskItemId.Should().BeNull(); - fetched.LinkedTaskTitleSnapshot.Should().Be("Will Be Deleted"); + Assert.NotNull(fetched); + Assert.Null(fetched!.TaskItemId); + Assert.Equal("Will Be Deleted", fetched.LinkedTaskTitleSnapshot); } } } diff --git a/TaskFlow.Api.Tests/Repositories/JournalNoteRepositoryTests.cs b/TaskFlow.Api.Tests/Repositories/JournalNoteRepositoryTests.cs index c4fe18f8..072360d4 100644 --- a/TaskFlow.Api.Tests/Repositories/JournalNoteRepositoryTests.cs +++ b/TaskFlow.Api.Tests/Repositories/JournalNoteRepositoryTests.cs @@ -1,4 +1,3 @@ -using FluentAssertions; using Microsoft.EntityFrameworkCore; using TaskFlow.Api.Data; using TaskFlow.Api.Models; @@ -31,9 +30,9 @@ public async Task GetAllByEntryIdAsync_ShouldReturnNotesOrderedByCreatedAt() var notes = (await repo.GetAllByEntryIdAsync(entry.Id)).ToList(); - notes.Should().HaveCount(2); - notes[0].Content.Should().Be("First"); - notes[1].Content.Should().Be("Second"); + Assert.Equal(2, notes.Count()); + Assert.Equal("First", notes[0].Content); + Assert.Equal("Second", notes[1].Content); } [Fact] @@ -51,8 +50,8 @@ public async Task GetAllByEntryIdAsync_ShouldNotReturnNotesFromOtherEntry() var notes = (await repo.GetAllByEntryIdAsync(entry1.Id)).ToList(); - notes.Should().HaveCount(1); - notes[0].Content.Should().Be("Entry1 note"); + Assert.Single(notes); + Assert.Equal("Entry1 note", notes[0].Content); } [Fact] @@ -68,9 +67,9 @@ public async Task GetByIdAsync_ShouldReturnNote_WhenExists() var fetched = await repo.GetByIdAsync(entry.Id, note.Id); - fetched.Should().NotBeNull(); - fetched!.Content.Should().Be("Test"); - fetched.JournalEntryId.Should().Be(entry.Id); + Assert.NotNull(fetched); + Assert.Equal("Test", fetched!.Content); + Assert.Equal(entry.Id, fetched.JournalEntryId); } [Fact] @@ -85,7 +84,7 @@ public async Task GetByIdAsync_ShouldReturnNull_WhenNotFound() var result = await repo.GetByIdAsync(entry.Id, 9999); - result.Should().BeNull(); + Assert.Null(result); } [Fact] @@ -102,7 +101,7 @@ public async Task GetByIdAsync_ShouldReturnNull_WhenEntryIdMismatch() var result = await repo.GetByIdAsync(entry2.Id, note.Id); - result.Should().BeNull(); + Assert.Null(result); } [Fact] @@ -116,9 +115,9 @@ public async Task AddAsync_ShouldPersistNoteWithId() var repo = new JournalNoteRepository(context); var note = await repo.AddAsync(new JournalNote { JournalEntryId = entry.Id, Content = "New note" }); - note.Id.Should().BeGreaterThan(0); - note.CreatedAt.Should().NotBe(default); - note.Content.Should().Be("New note"); + Assert.True(note.Id > 0); + Assert.NotEqual(default, note.CreatedAt); + Assert.Equal("New note", note.Content); } [Fact] @@ -136,9 +135,9 @@ public async Task UpdateAsync_ShouldSetUpdatedAt() await repo.UpdateAsync(note); var fetched = await repo.GetByIdAsync(entry.Id, note.Id); - fetched.Should().NotBeNull(); - fetched!.Content.Should().Be("Updated"); - fetched.UpdatedAt.Should().NotBeNull(); + Assert.NotNull(fetched); + Assert.Equal("Updated", fetched!.Content); + Assert.NotNull(fetched.UpdatedAt); } [Fact] @@ -155,7 +154,7 @@ public async Task DeleteAsync_ShouldRemoveNote() await repo.DeleteAsync(entry.Id, note.Id); var fetched = await repo.GetByIdAsync(entry.Id, note.Id); - fetched.Should().BeNull(); + Assert.Null(fetched); } [Fact] @@ -170,6 +169,6 @@ public async Task DeleteAsync_ShouldNotThrow_WhenNoteNotFound() var act = async () => await repo.DeleteAsync(entry.Id, 9999); - await act.Should().NotThrowAsync(); + Assert.Null(await Record.ExceptionAsync(act)); } } diff --git a/TaskFlow.Api.Tests/Repositories/NoteRepositoryTests.cs b/TaskFlow.Api.Tests/Repositories/NoteRepositoryTests.cs index 468fc43b..9d8c1ee6 100644 --- a/TaskFlow.Api.Tests/Repositories/NoteRepositoryTests.cs +++ b/TaskFlow.Api.Tests/Repositories/NoteRepositoryTests.cs @@ -1,4 +1,3 @@ -using FluentAssertions; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using TaskFlow.Api.Data; @@ -41,8 +40,8 @@ public async Task GetAllByTaskIdAsync_ShouldReturnNotesForTask() var result = await repo.GetAllByTaskIdAsync(1); - result.Should().HaveCount(2); - result.Should().AllSatisfy(n => n.TaskItemId.Should().Be(1)); + Assert.Equal(2, result.Count()); + Assert.All(result, n => Assert.Equal(1, n.TaskItemId)); } [Fact] @@ -54,7 +53,7 @@ public async Task GetAllByTaskIdAsync_ShouldReturnEmpty_WhenTaskHasNoNotes() var result = await repo.GetAllByTaskIdAsync(1); - result.Should().BeEmpty(); + Assert.Empty(result); } [Fact] @@ -69,9 +68,9 @@ public async Task GetByIdAsync_ShouldReturnNote_WhenNoteExistsForTask() var result = await repo.GetByIdAsync(1, note.Id); - result.Should().NotBeNull(); - result!.Content.Should().Be("Test note"); - result.TaskItemId.Should().Be(1); + Assert.NotNull(result); + Assert.Equal("Test note", result!.Content); + Assert.Equal(1, result.TaskItemId); } [Fact] @@ -87,7 +86,7 @@ public async Task GetByIdAsync_ShouldReturnNull_WhenNoteDoesNotBelongToTask() var result = await repo.GetByIdAsync(1, note.Id); - result.Should().BeNull(); + Assert.Null(result); } [Fact] @@ -99,7 +98,7 @@ public async Task GetByIdAsync_ShouldReturnNull_WhenNoteDoesNotExist() var result = await repo.GetByIdAsync(1, 999); - result.Should().BeNull(); + Assert.Null(result); } [Fact] @@ -112,13 +111,13 @@ public async Task AddAsync_ShouldAddNoteAndReturnIt() var result = await repo.AddAsync(note); - result.Id.Should().BeGreaterThan(0); - result.Content.Should().Be("New note"); - result.TaskItemId.Should().Be(1); - result.CreatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5)); + Assert.True(result.Id > 0); + Assert.Equal("New note", result.Content); + Assert.Equal(1, result.TaskItemId); + Assert.Equal(DateTime.UtcNow, result.CreatedAt, TimeSpan.FromSeconds(5)); var saved = await context.Notes.FindAsync(result.Id); - saved.Should().NotBeNull(); + Assert.NotNull(saved); } [Fact] @@ -135,9 +134,9 @@ public async Task UpdateAsync_ShouldUpdateNoteContent() await repo.UpdateAsync(note); var updated = await context.Notes.FindAsync(note.Id); - updated!.Content.Should().Be("Updated"); - updated.UpdatedAt.Should().NotBeNull(); - updated.UpdatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5)); + Assert.Equal("Updated", updated!.Content); + Assert.NotNull(updated.UpdatedAt); + Assert.Equal(DateTime.UtcNow, updated.UpdatedAt.Value, TimeSpan.FromSeconds(5)); } [Fact] @@ -153,7 +152,7 @@ public async Task DeleteAsync_ShouldRemoveNote_WhenNoteExists() await repo.DeleteAsync(1, note.Id); var deleted = await context.Notes.FindAsync(note.Id); - deleted.Should().BeNull(); + Assert.Null(deleted); } [Fact] @@ -164,7 +163,7 @@ public async Task DeleteAsync_ShouldNotThrow_WhenNoteDoesNotExist() var act = async () => await repo.DeleteAsync(1, 999); - await act.Should().NotThrowAsync(); + Assert.Null(await Record.ExceptionAsync(act)); } [Fact] @@ -181,7 +180,7 @@ public async Task DeleteAsync_ShouldNotDeleteNote_WhenNoteDoesNotBelongToTask() await repo.DeleteAsync(1, note.Id); var stillExists = await context.Notes.FindAsync(note.Id); - stillExists.Should().NotBeNull(); + Assert.NotNull(stillExists); } [Fact] @@ -212,7 +211,7 @@ public async Task DeleteTaskAsync_ShouldCascadeDeleteNotes() await using (var context = new TaskDbContext(options)) { var remainingNotes = await context.Notes.ToListAsync(); - remainingNotes.Should().BeEmpty(); + Assert.Empty(remainingNotes); } connection.Close(); diff --git a/TaskFlow.Api.Tests/Repositories/ProjectRepositoryTests.cs b/TaskFlow.Api.Tests/Repositories/ProjectRepositoryTests.cs index 954d800c..bccbb42f 100644 --- a/TaskFlow.Api.Tests/Repositories/ProjectRepositoryTests.cs +++ b/TaskFlow.Api.Tests/Repositories/ProjectRepositoryTests.cs @@ -1,4 +1,3 @@ -using FluentAssertions; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using TaskFlow.Api.Data; @@ -36,13 +35,13 @@ public async Task AddAsync_ShouldSetCreatedAt_AndUpdateAsyncShouldSetUpdatedAt() var repo = new ProjectRepository(context); var project = await repo.AddAsync(new Project { Name = "Alpha" }); - project.CreatedAt.Should().NotBe(default); - project.UpdatedAt.Should().BeNull(); + Assert.NotEqual(default, project.CreatedAt); + Assert.Null(project.UpdatedAt); project.Name = "Alpha 2"; await repo.UpdateAsync(project); - (await repo.GetByIdAsync(project.Id))!.UpdatedAt.Should().NotBeNull(); + Assert.NotNull((await repo.GetByIdAsync(project.Id))!.UpdatedAt); } [Fact] @@ -57,8 +56,8 @@ public async Task GetAllAsync_ShouldReturnProjectsOrderedByName_WithTasks() var all = (await repo.GetAllAsync()).ToList(); - all.Select(p => p.Name).Should().ContainInOrder("Alpha", "Bravo"); - all.Single(p => p.Name == "Bravo").Tasks.Should().HaveCount(1); + Assert.Equal(new[] { "Alpha", "Bravo" }, all.Select(p => p.Name).Where(n => n is "Alpha" or "Bravo")); + Assert.Single(all.Single(p => p.Name == "Bravo").Tasks); } [Fact] @@ -68,9 +67,9 @@ public async Task NameExistsAsync_ShouldHonorExcludeId() var repo = new ProjectRepository(context); var p = await repo.AddAsync(new Project { Name = "Alpha" }); - (await repo.NameExistsAsync("Alpha")).Should().BeTrue(); - (await repo.NameExistsAsync("Alpha", p.Id)).Should().BeFalse(); - (await repo.NameExistsAsync("Other")).Should().BeFalse(); + Assert.True((await repo.NameExistsAsync("Alpha"))); + Assert.False((await repo.NameExistsAsync("Alpha", p.Id))); + Assert.False((await repo.NameExistsAsync("Other"))); } [Fact] @@ -88,8 +87,8 @@ public async Task GetTasksAsync_ShouldReturnOnlyProjectTasks() var tasks = (await repo.GetTasksAsync(p1.Id)).ToList(); - tasks.Should().ContainSingle().Which.Title.Should().Be("1"); - tasks[0].Project!.Name.Should().Be("A"); + Assert.Equal("1", Assert.Single(tasks).Title); + Assert.Equal("A", tasks[0].Project!.Name); } [Fact] @@ -100,7 +99,7 @@ public async Task DeleteAsync_ShouldNoOp_WhenMissing() var act = () => repo.DeleteAsync(123); - await act.Should().NotThrowAsync(); + Assert.Null(await Record.ExceptionAsync(act)); } [Fact] @@ -110,8 +109,8 @@ public async Task AssignTaskAsync_ShouldReturnProjectNotFound_AndTaskNotFound() var repo = new ProjectRepository(context); var p = await repo.AddAsync(new Project { Name = "A" }); - (await repo.AssignTaskAsync(999, 1)).Should().Be(AssignTaskResult.ProjectNotFound); - (await repo.AssignTaskAsync(p.Id, 999)).Should().Be(AssignTaskResult.TaskNotFound); + Assert.Equal(AssignTaskResult.ProjectNotFound, (await repo.AssignTaskAsync(999, 1))); + Assert.Equal(AssignTaskResult.TaskNotFound, (await repo.AssignTaskAsync(p.Id, 999))); } [Fact] @@ -124,14 +123,14 @@ public async Task AssignTaskAsync_ShouldAssign_RecordEvent_AndRejectDuplicate() context.TaskItems.Add(task); await context.SaveChangesAsync(); - (await repo.AssignTaskAsync(p.Id, task.Id)).Should().Be(AssignTaskResult.Success); - (await repo.AssignTaskAsync(p.Id, task.Id)).Should().Be(AssignTaskResult.AlreadyAssigned); + Assert.Equal(AssignTaskResult.Success, (await repo.AssignTaskAsync(p.Id, task.Id))); + Assert.Equal(AssignTaskResult.AlreadyAssigned, (await repo.AssignTaskAsync(p.Id, task.Id))); var saved = await context.TaskItems.AsNoTracking().SingleAsync(); - saved.ProjectId.Should().Be(p.Id); + Assert.Equal(p.Id, saved.ProjectId); var events = await context.TaskItemEvents.Where(e => e.TaskItemId == task.Id).ToListAsync(); - events.Should().ContainSingle(e => e.EventType == "ProjectChanged") - .Which.ChangeSummary.Should().Be("Assigned to project 'Alpha'."); + var projectChanged = Assert.Single(events, e => e.EventType == "ProjectChanged"); + Assert.Equal("Assigned to project 'Alpha'.", projectChanged.ChangeSummary); } [Fact] @@ -145,10 +144,10 @@ public async Task AssignTaskAsync_ShouldMoveTaskBetweenProjects() context.TaskItems.Add(task); await context.SaveChangesAsync(); - (await repo.AssignTaskAsync(b.Id, task.Id)).Should().Be(AssignTaskResult.Success); + Assert.Equal(AssignTaskResult.Success, (await repo.AssignTaskAsync(b.Id, task.Id))); - (await context.TaskItems.AsNoTracking().SingleAsync()).ProjectId.Should().Be(b.Id); - (await context.TaskItemEvents.SingleAsync()).ChangeSummary.Should().Be("Project changed from 'A' to 'B'."); + Assert.Equal(b.Id, (await context.TaskItems.AsNoTracking().SingleAsync()).ProjectId); + Assert.Equal("Project changed from 'A' to 'B'.", (await context.TaskItemEvents.SingleAsync()).ChangeSummary); } [Fact] @@ -161,10 +160,10 @@ public async Task UnassignTaskAsync_ShouldClearProject_AndRecordEvent() context.TaskItems.Add(task); await context.SaveChangesAsync(); - (await repo.UnassignTaskAsync(p.Id, task.Id)).Should().BeTrue(); + Assert.True((await repo.UnassignTaskAsync(p.Id, task.Id))); - (await context.TaskItems.AsNoTracking().SingleAsync()).ProjectId.Should().BeNull(); - (await context.TaskItemEvents.SingleAsync()).ChangeSummary.Should().Be("Removed from project 'Alpha'."); + Assert.Null((await context.TaskItems.AsNoTracking().SingleAsync()).ProjectId); + Assert.Equal("Removed from project 'Alpha'.", (await context.TaskItemEvents.SingleAsync()).ChangeSummary); } [Fact] @@ -177,7 +176,7 @@ public async Task UnassignTaskAsync_ShouldReturnFalse_WhenTaskNotInProject() context.TaskItems.Add(task); await context.SaveChangesAsync(); - (await repo.UnassignTaskAsync(p.Id, task.Id)).Should().BeFalse(); + Assert.False((await repo.UnassignTaskAsync(p.Id, task.Id))); } [Fact] @@ -196,8 +195,8 @@ public async Task DeleteAsync_ShouldKeepTasks_AndClearTheirProject_OnSqlite() await repo.DeleteAsync(p.Id); var task = await context.TaskItems.AsNoTracking().SingleAsync(); - task.ProjectId.Should().BeNull(); - (await context.Projects.CountAsync()).Should().Be(0); + Assert.Null(task.ProjectId); + Assert.Equal(0, (await context.Projects.CountAsync())); } } @@ -211,10 +210,10 @@ public async Task Name_ShouldBeUniqueCaseInsensitively_OnSqlite() var repo = new ProjectRepository(context); await repo.AddAsync(new Project { Name = "Alpha" }); - (await repo.NameExistsAsync("alpha")).Should().BeTrue(); + Assert.True((await repo.NameExistsAsync("alpha"))); var act = () => repo.AddAsync(new Project { Name = "ALPHA" }); - await act.Should().ThrowAsync(); + await Assert.ThrowsAsync(act); } } } diff --git a/TaskFlow.Api.Tests/Repositories/TaskRepositoryTests.cs b/TaskFlow.Api.Tests/Repositories/TaskRepositoryTests.cs index d7f196d9..ceb70e65 100644 --- a/TaskFlow.Api.Tests/Repositories/TaskRepositoryTests.cs +++ b/TaskFlow.Api.Tests/Repositories/TaskRepositoryTests.cs @@ -1,4 +1,3 @@ -using FluentAssertions; using Microsoft.EntityFrameworkCore; using TaskFlow.Api.Data; using TaskFlow.Api.Models; @@ -35,8 +34,8 @@ public async Task GetAllAsync_ShouldReturnAllTasks() var result = await repository.GetAllAsync(); // Assert - result.Should().HaveCount(2); - result.Should().AllSatisfy(t => t.Status.Should().BeOneOf(Status.Draft, Status.Todo, Status.Completed)); + Assert.Equal(2, result.Count()); + Assert.All(result, t => Assert.Contains(t.Status, new[] { Status.Draft, Status.Todo, Status.Completed })); } [Fact] @@ -50,7 +49,7 @@ public async Task GetAllAsync_ShouldReturnEmptyList_WhenNoTasks() var result = await repository.GetAllAsync(); // Assert - result.Should().BeEmpty(); + Assert.Empty(result); } [Fact] @@ -75,9 +74,9 @@ public async Task GetByIdAsync_ShouldReturnTask_WhenTaskExists() var result = await repository.GetByIdAsync(1); // Assert - result.Should().NotBeNull(); - result!.Status.Should().Be(Status.Todo); - result.Title.Should().Be("Task 1"); + Assert.NotNull(result); + Assert.Equal(Status.Todo, result!.Status); + Assert.Equal("Task 1", result.Title); } [Fact] @@ -91,7 +90,7 @@ public async Task GetByIdAsync_ShouldReturnNull_WhenTaskDoesNotExist() var result = await repository.GetByIdAsync(999); // Assert - result.Should().BeNull(); + Assert.Null(result); } [Fact] @@ -106,16 +105,17 @@ public async Task AddAsync_ShouldAddTaskAndReturnIt() var result = await repository.AddAsync(newTask); // Assert - result.Should().NotBeNull(); - result.Id.Should().BeGreaterThan(0); - result.Title.Should().Be("New Task"); + Assert.NotNull(result); + Assert.True(result.Id > 0); + Assert.Equal("New Task", result.Title); var savedTask = await context.TaskItems.FindAsync(result.Id); - savedTask.Should().NotBeNull(); - savedTask.Should().BeEquivalentTo(result); + Assert.NotNull(savedTask); + // FindAsync returns the tracked instance AddAsync saved, so this is the same object + Assert.Same(result, savedTask); var createdEvent = await context.TaskItemEvents.SingleAsync(e => e.TaskItemId == result.Id); - createdEvent.EventType.Should().Be("TaskCreated"); + Assert.Equal("TaskCreated", createdEvent.EventType); } [Fact] @@ -130,12 +130,12 @@ public async Task AddAsync_ShouldHandleTaskWithNullDescription() var result = await repository.AddAsync(newTask); // Assert - result.Should().NotBeNull(); - result.Description.Should().BeNull(); + Assert.NotNull(result); + Assert.Null(result.Description); var savedTask = await context.TaskItems.FindAsync(result.Id); - savedTask.Should().NotBeNull(); - savedTask!.Description.Should().BeNull(); + Assert.NotNull(savedTask); + Assert.Null(savedTask!.Description); } [Fact] @@ -150,12 +150,12 @@ public async Task AddAsync_ShouldHandleCompletedTask() var result = await repository.AddAsync(newTask); // Assert - result.Should().NotBeNull(); - result.IsComplete.Should().BeTrue(); + Assert.NotNull(result); + Assert.True(result.IsComplete); var savedTask = await context.TaskItems.FindAsync(result.Id); - savedTask.Should().NotBeNull(); - savedTask!.IsComplete.Should().BeTrue(); + Assert.NotNull(savedTask); + Assert.True(savedTask!.IsComplete); } [Fact] @@ -178,10 +178,10 @@ public async Task UpdateAsync_ShouldUpdateExistingTask() // Assert var updatedTask = await context.TaskItems.FindAsync(1); - updatedTask.Should().NotBeNull(); - updatedTask!.Title.Should().Be("Updated"); - updatedTask.Description.Should().Be("Updated Description"); - updatedTask.IsComplete.Should().BeTrue(); + Assert.NotNull(updatedTask); + Assert.Equal("Updated", updatedTask!.Title); + Assert.Equal("Updated Description", updatedTask.Description); + Assert.True(updatedTask.IsComplete); } [Fact] @@ -202,10 +202,10 @@ public async Task UpdateAsync_ShouldHandlePartialUpdate() // Assert var updatedTask = await context.TaskItems.FindAsync(1); - updatedTask.Should().NotBeNull(); - updatedTask!.Title.Should().Be("Original"); - updatedTask.Description.Should().Be("Description"); - updatedTask.IsComplete.Should().BeTrue(); + Assert.NotNull(updatedTask); + Assert.Equal("Original", updatedTask!.Title); + Assert.Equal("Description", updatedTask.Description); + Assert.True(updatedTask.IsComplete); } [Fact] @@ -219,7 +219,7 @@ public async Task UpdateAsync_ShouldWriteTitleAndPriorityHistoryEvents_WhenValue context.ChangeTracker.Clear(); var tracked = await context.TaskItems.FindAsync(1); - tracked.Should().NotBeNull(); + Assert.NotNull(tracked); tracked!.Title = "Updated"; tracked.Priority = Priority.High; @@ -230,8 +230,8 @@ public async Task UpdateAsync_ShouldWriteTitleAndPriorityHistoryEvents_WhenValue .OrderBy(e => e.OccurredAtUtc) .ToListAsync(); - events.Should().Contain(e => e.EventType == "TitleChanged"); - events.Should().Contain(e => e.EventType == "PriorityChanged"); + Assert.Contains(events, e => e.EventType == "TitleChanged"); + Assert.Contains(events, e => e.EventType == "PriorityChanged"); } [Fact] @@ -245,14 +245,14 @@ public async Task UpdateAsync_ShouldWriteCompletedAndReopenedEvents_WhenCompleti context.ChangeTracker.Clear(); var tracked = await context.TaskItems.FindAsync(1); - tracked.Should().NotBeNull(); + Assert.NotNull(tracked); tracked!.IsComplete = true; tracked.Status = Status.Completed; await repository.UpdateAsync(tracked); context.ChangeTracker.Clear(); tracked = await context.TaskItems.FindAsync(1); - tracked.Should().NotBeNull(); + Assert.NotNull(tracked); tracked!.IsComplete = false; tracked.Status = Status.Todo; await repository.UpdateAsync(tracked); @@ -262,8 +262,8 @@ public async Task UpdateAsync_ShouldWriteCompletedAndReopenedEvents_WhenCompleti .OrderBy(e => e.OccurredAtUtc) .ToListAsync(); - events.Should().Contain(e => e.EventType == "Completed"); - events.Should().Contain(e => e.EventType == "Reopened"); + Assert.Contains(events, e => e.EventType == "Completed"); + Assert.Contains(events, e => e.EventType == "Reopened"); } [Fact] @@ -286,7 +286,7 @@ public async Task UpdateAsync_ShouldWriteDescriptionAndDueDateHistoryEvents_When context.ChangeTracker.Clear(); var tracked = await context.TaskItems.FindAsync(1); - tracked.Should().NotBeNull(); + Assert.NotNull(tracked); tracked!.Description = "Updated description"; tracked.DueDate = dueDate.AddDays(3); @@ -297,8 +297,8 @@ public async Task UpdateAsync_ShouldWriteDescriptionAndDueDateHistoryEvents_When .OrderBy(e => e.OccurredAtUtc) .ToListAsync(); - events.Should().Contain(e => e.EventType == "DescriptionChanged"); - events.Should().Contain(e => e.EventType == "DueDateChanged"); + Assert.Contains(events, e => e.EventType == "DescriptionChanged"); + Assert.Contains(events, e => e.EventType == "DueDateChanged"); } [Fact] @@ -316,7 +316,7 @@ public async Task DeleteAsync_ShouldRemoveTask_WhenTaskExists() // Assert var deletedTask = await context.TaskItems.FindAsync(1); - deletedTask.Should().BeNull(); + Assert.Null(deletedTask); } [Fact] @@ -330,7 +330,7 @@ public async Task DeleteAsync_ShouldNotThrow_WhenTaskDoesNotExist() var act = async () => await repository.DeleteAsync(999); // Assert - await act.Should().NotThrowAsync(); + Assert.Null(await Record.ExceptionAsync(act)); } [Fact] @@ -352,7 +352,7 @@ public async Task DeleteAsync_ShouldOnlyDeleteSpecifiedTask() // Assert var remainingTasks = await context.TaskItems.ToListAsync(); - remainingTasks.Should().HaveCount(1); - remainingTasks.First().Id.Should().Be(2); + Assert.Single(remainingTasks); + Assert.Equal(2, remainingTasks.First().Id); } } diff --git a/TaskFlow.Api.Tests/TaskFlow.Api.Tests.csproj b/TaskFlow.Api.Tests/TaskFlow.Api.Tests.csproj index ab733560..d85dab4b 100644 --- a/TaskFlow.Api.Tests/TaskFlow.Api.Tests.csproj +++ b/TaskFlow.Api.Tests/TaskFlow.Api.Tests.csproj @@ -13,7 +13,6 @@ - diff --git a/TaskFlow.Api.Tests/Validators/AddJournalTodoDtoValidatorTests.cs b/TaskFlow.Api.Tests/Validators/AddJournalTodoDtoValidatorTests.cs index a96fee76..42928e12 100644 --- a/TaskFlow.Api.Tests/Validators/AddJournalTodoDtoValidatorTests.cs +++ b/TaskFlow.Api.Tests/Validators/AddJournalTodoDtoValidatorTests.cs @@ -1,4 +1,3 @@ -using FluentAssertions; using FluentValidation; using TaskFlow.Api.DTOs; using TaskFlow.Api.Validators; @@ -17,8 +16,8 @@ public async Task Validate_ShouldFail_WhenTaskItemIdIsNotPositive(int id) { var result = await _validator.ValidateAsync(new AddJournalTodoDto { TaskItemId = id }); - result.IsValid.Should().BeFalse(); - result.Errors.Should().ContainSingle(e => + Assert.False(result.IsValid); + Assert.Single(result.Errors, e => e.PropertyName == "TaskItemId" && e.ErrorMessage == "TaskItemId must be greater than 0."); } @@ -30,7 +29,7 @@ public async Task Validate_ShouldPass_WhenTaskItemIdIsPositive(int id) { var result = await _validator.ValidateAsync(new AddJournalTodoDto { TaskItemId = id }); - result.IsValid.Should().BeTrue(); + Assert.True(result.IsValid); } [Theory] @@ -41,8 +40,8 @@ public async Task Validate_ShouldFail_WhenTimezoneOffsetMinutesIsOutOfRange(int { var result = await _validator.ValidateAsync(new AddJournalTodoDto { TaskItemId = 1, TimezoneOffsetMinutes = value }); - result.IsValid.Should().BeFalse(); - result.Errors.Should().ContainSingle(e => + Assert.False(result.IsValid); + Assert.Single(result.Errors, e => e.PropertyName == "TimezoneOffsetMinutes" && e.ErrorMessage == "TimezoneOffsetMinutes must be between -720 and 840 (UTC-12:00 to UTC+14:00)."); } @@ -55,7 +54,7 @@ public async Task Validate_ShouldPass_WhenTimezoneOffsetMinutesIsWithinRange(int { var result = await _validator.ValidateAsync(new AddJournalTodoDto { TaskItemId = 1, TimezoneOffsetMinutes = value }); - result.IsValid.Should().BeTrue(); + Assert.True(result.IsValid); } [Fact] @@ -63,6 +62,6 @@ public async Task Validate_ShouldPass_WhenTimezoneOffsetMinutesIsNull() { var result = await _validator.ValidateAsync(new AddJournalTodoDto { TaskItemId = 1, TimezoneOffsetMinutes = null }); - result.IsValid.Should().BeTrue(); + Assert.True(result.IsValid); } } diff --git a/TaskFlow.Api.Tests/Validators/CarryForwardRequestDtoValidatorTests.cs b/TaskFlow.Api.Tests/Validators/CarryForwardRequestDtoValidatorTests.cs index 327e1a7c..add37a53 100644 --- a/TaskFlow.Api.Tests/Validators/CarryForwardRequestDtoValidatorTests.cs +++ b/TaskFlow.Api.Tests/Validators/CarryForwardRequestDtoValidatorTests.cs @@ -1,4 +1,3 @@ -using FluentAssertions; using TaskFlow.Api.DTOs; using TaskFlow.Api.Validators; @@ -15,7 +14,7 @@ public class CarryForwardRequestDtoValidatorTests [InlineData(840)] public void Validate_ShouldBeValid_ForNullOrInRangeOffset(int? offset) { - _validator.Validate(new CarryForwardRequestDto { TimezoneOffsetMinutes = offset }).IsValid.Should().BeTrue(); + Assert.True(_validator.Validate(new CarryForwardRequestDto { TimezoneOffsetMinutes = offset }).IsValid); } [Theory] @@ -23,6 +22,6 @@ public void Validate_ShouldBeValid_ForNullOrInRangeOffset(int? offset) [InlineData(841)] public void Validate_ShouldBeInvalid_ForOutOfRangeOffset(int offset) { - _validator.Validate(new CarryForwardRequestDto { TimezoneOffsetMinutes = offset }).IsValid.Should().BeFalse(); + Assert.False(_validator.Validate(new CarryForwardRequestDto { TimezoneOffsetMinutes = offset }).IsValid); } } diff --git a/TaskFlow.Api.Tests/Validators/CreateJournalEntryDtoValidatorTests.cs b/TaskFlow.Api.Tests/Validators/CreateJournalEntryDtoValidatorTests.cs index bbf7ceca..558f1708 100644 --- a/TaskFlow.Api.Tests/Validators/CreateJournalEntryDtoValidatorTests.cs +++ b/TaskFlow.Api.Tests/Validators/CreateJournalEntryDtoValidatorTests.cs @@ -1,4 +1,3 @@ -using FluentAssertions; using TaskFlow.Api.DTOs; using TaskFlow.Api.Validators; @@ -17,7 +16,7 @@ public void Validate_ShouldBeValid_ForNullOrInRangeOffset(int? offset) { var result = _validator.Validate(new CreateJournalEntryDto { Title = "t", TimezoneOffsetMinutes = offset }); - result.IsValid.Should().BeTrue(); + Assert.True(result.IsValid); } [Theory] @@ -27,6 +26,6 @@ public void Validate_ShouldBeInvalid_ForOutOfRangeOffset(int offset) { var result = _validator.Validate(new CreateJournalEntryDto { Title = "t", TimezoneOffsetMinutes = offset }); - result.IsValid.Should().BeFalse(); + Assert.False(result.IsValid); } } diff --git a/TaskFlow.Api.Tests/Validators/JournalEntryValidatorTests.cs b/TaskFlow.Api.Tests/Validators/JournalEntryValidatorTests.cs index f64a09d5..bd82dd76 100644 --- a/TaskFlow.Api.Tests/Validators/JournalEntryValidatorTests.cs +++ b/TaskFlow.Api.Tests/Validators/JournalEntryValidatorTests.cs @@ -1,4 +1,3 @@ -using FluentAssertions; using TaskFlow.Api.Models; using TaskFlow.Api.Validators; @@ -15,8 +14,8 @@ public async Task Validate_ShouldFail_WhenDateIsDefault() var result = await _validator.ValidateAsync(entry); - result.IsValid.Should().BeFalse(); - result.Errors.Should().Contain(e => e.PropertyName == "Date" && e.ErrorMessage == "Date is required."); + Assert.False(result.IsValid); + Assert.Contains(result.Errors, e => e.PropertyName == "Date" && e.ErrorMessage == "Date is required."); } [Fact] @@ -26,8 +25,8 @@ public async Task Validate_ShouldFail_WhenSummaryTooLong() var result = await _validator.ValidateAsync(entry); - result.IsValid.Should().BeFalse(); - result.Errors.Should().Contain(e => e.PropertyName == "Summary" && e.ErrorMessage == "Summary must not exceed 10000 characters."); + Assert.False(result.IsValid); + Assert.Contains(result.Errors, e => e.PropertyName == "Summary" && e.ErrorMessage == "Summary must not exceed 10000 characters."); } [Fact] @@ -37,6 +36,6 @@ public async Task Validate_ShouldPass_WhenValid() var result = await _validator.ValidateAsync(entry); - result.IsValid.Should().BeTrue(); + Assert.True(result.IsValid); } } diff --git a/TaskFlow.Api.Tests/Validators/JournalLogEntryValidatorTests.cs b/TaskFlow.Api.Tests/Validators/JournalLogEntryValidatorTests.cs index 9db5a8e9..c0a8cfbc 100644 --- a/TaskFlow.Api.Tests/Validators/JournalLogEntryValidatorTests.cs +++ b/TaskFlow.Api.Tests/Validators/JournalLogEntryValidatorTests.cs @@ -1,4 +1,3 @@ -using FluentAssertions; using TaskFlow.Api.Models; using TaskFlow.Api.Validators; @@ -15,8 +14,8 @@ public async Task Validate_ShouldFail_WhenContentIsEmpty() var result = await _validator.ValidateAsync(log); - result.IsValid.Should().BeFalse(); - result.Errors.Should().Contain(e => e.PropertyName == "Content"); + Assert.False(result.IsValid); + Assert.Contains(result.Errors, e => e.PropertyName == "Content"); } [Fact] @@ -26,7 +25,7 @@ public async Task Validate_ShouldPass_WhenValid() var result = await _validator.ValidateAsync(log); - result.IsValid.Should().BeTrue(); + Assert.True(result.IsValid); } [Fact] @@ -36,7 +35,7 @@ public async Task Validate_ShouldPass_WhenTaskItemIdIsNull() var result = await _validator.ValidateAsync(log); - result.IsValid.Should().BeTrue(); + Assert.True(result.IsValid); } [Fact] @@ -46,7 +45,7 @@ public async Task Validate_ShouldPass_WhenTaskItemIdIsPositive() var result = await _validator.ValidateAsync(log); - result.IsValid.Should().BeTrue(); + Assert.True(result.IsValid); } [Fact] @@ -56,8 +55,8 @@ public async Task Validate_ShouldFail_WhenTaskItemIdIsZero() var result = await _validator.ValidateAsync(log); - result.IsValid.Should().BeFalse(); - result.Errors.Should().Contain(e => e.PropertyName == "TaskItemId"); + Assert.False(result.IsValid); + Assert.Contains(result.Errors, e => e.PropertyName == "TaskItemId"); } [Fact] @@ -67,7 +66,7 @@ public async Task Validate_ShouldFail_WhenTaskItemIdIsNegative() var result = await _validator.ValidateAsync(log); - result.IsValid.Should().BeFalse(); - result.Errors.Should().Contain(e => e.PropertyName == "TaskItemId"); + Assert.False(result.IsValid); + Assert.Contains(result.Errors, e => e.PropertyName == "TaskItemId"); } } diff --git a/TaskFlow.Api.Tests/Validators/JournalNoteValidatorTests.cs b/TaskFlow.Api.Tests/Validators/JournalNoteValidatorTests.cs index 22a9c824..3753e5c7 100644 --- a/TaskFlow.Api.Tests/Validators/JournalNoteValidatorTests.cs +++ b/TaskFlow.Api.Tests/Validators/JournalNoteValidatorTests.cs @@ -1,4 +1,3 @@ -using FluentAssertions; using TaskFlow.Api.Models; using TaskFlow.Api.Validators; @@ -15,7 +14,7 @@ public async Task Validate_ShouldPass_WhenContentProvided() var result = await _validator.ValidateAsync(note); - result.IsValid.Should().BeTrue(); + Assert.True(result.IsValid); } [Fact] @@ -25,8 +24,8 @@ public async Task Validate_ShouldFail_WhenContentIsEmpty() var result = await _validator.ValidateAsync(note); - result.IsValid.Should().BeFalse(); - result.Errors.Should().Contain(e => e.PropertyName == "Content"); + Assert.False(result.IsValid); + Assert.Contains(result.Errors, e => e.PropertyName == "Content"); } [Fact] @@ -36,8 +35,8 @@ public async Task Validate_ShouldFail_WhenContentIsWhitespace() var result = await _validator.ValidateAsync(note); - result.IsValid.Should().BeFalse(); - result.Errors.Should().Contain(e => e.PropertyName == "Content"); + Assert.False(result.IsValid); + Assert.Contains(result.Errors, e => e.PropertyName == "Content"); } [Fact] @@ -47,8 +46,8 @@ public async Task Validate_ShouldFail_WhenContentExceedsMaxLength() var result = await _validator.ValidateAsync(note); - result.IsValid.Should().BeFalse(); - result.Errors.Should().Contain(e => + Assert.False(result.IsValid); + Assert.Contains(result.Errors, e => e.PropertyName == "Content" && e.ErrorMessage == "Content must not exceed 2000 characters."); } @@ -60,7 +59,7 @@ public async Task Validate_ShouldPass_WhenContentIsExactly2000Characters() var result = await _validator.ValidateAsync(note); - result.IsValid.Should().BeTrue(); - result.Errors.Should().BeEmpty(); + Assert.True(result.IsValid); + Assert.Empty(result.Errors); } } diff --git a/TaskFlow.Api.Tests/Validators/NoteValidatorTests.cs b/TaskFlow.Api.Tests/Validators/NoteValidatorTests.cs index 09577d78..c8b91a42 100644 --- a/TaskFlow.Api.Tests/Validators/NoteValidatorTests.cs +++ b/TaskFlow.Api.Tests/Validators/NoteValidatorTests.cs @@ -1,4 +1,3 @@ -using FluentAssertions; using TaskFlow.Api.Models; using TaskFlow.Api.Validators; @@ -20,8 +19,8 @@ public async Task Validate_ShouldPass_WhenNoteIsValid() var result = await _validator.ValidateAsync(note); - result.IsValid.Should().BeTrue(); - result.Errors.Should().BeEmpty(); + Assert.True(result.IsValid); + Assert.Empty(result.Errors); } [Fact] @@ -31,8 +30,8 @@ public async Task Validate_ShouldFail_WhenContentIsEmpty() var result = await _validator.ValidateAsync(note); - result.IsValid.Should().BeFalse(); - result.Errors.Should().Contain(e => + Assert.False(result.IsValid); + Assert.Contains(result.Errors, e => e.PropertyName == "Content" && e.ErrorMessage == "Content is required."); } @@ -44,8 +43,8 @@ public async Task Validate_ShouldFail_WhenContentExceedsMaxLength() var result = await _validator.ValidateAsync(note); - result.IsValid.Should().BeFalse(); - result.Errors.Should().Contain(e => + Assert.False(result.IsValid); + Assert.Contains(result.Errors, e => e.PropertyName == "Content" && e.ErrorMessage == "Content must not exceed 2000 characters."); } @@ -57,7 +56,7 @@ public async Task Validate_ShouldPass_WhenContentIsExactly2000Characters() var result = await _validator.ValidateAsync(note); - result.IsValid.Should().BeTrue(); - result.Errors.Should().BeEmpty(); + Assert.True(result.IsValid); + Assert.Empty(result.Errors); } } diff --git a/TaskFlow.Api.Tests/Validators/ProjectValidatorTests.cs b/TaskFlow.Api.Tests/Validators/ProjectValidatorTests.cs index a8e1860e..e8104e6e 100644 --- a/TaskFlow.Api.Tests/Validators/ProjectValidatorTests.cs +++ b/TaskFlow.Api.Tests/Validators/ProjectValidatorTests.cs @@ -1,4 +1,3 @@ -using FluentAssertions; using TaskFlow.Api.DTOs; using TaskFlow.Api.Models; using TaskFlow.Api.Validators; @@ -14,7 +13,7 @@ public async Task Validate_ShouldPass_WhenProjectIsValid() { var result = await _validator.ValidateAsync(new Project { Name = "Alpha", Description = "Work" }); - result.IsValid.Should().BeTrue(); + Assert.True(result.IsValid); } [Fact] @@ -22,7 +21,7 @@ public async Task Validate_ShouldFail_WhenNameIsEmpty() { var result = await _validator.ValidateAsync(new Project { Name = string.Empty }); - result.Errors.Should().Contain(e => e.PropertyName == "Name" && e.ErrorMessage == "Name is required."); + Assert.Contains(result.Errors, e => e.PropertyName == "Name" && e.ErrorMessage == "Name is required."); } [Fact] @@ -30,7 +29,7 @@ public async Task Validate_ShouldFail_WhenNameExceeds100Characters() { var result = await _validator.ValidateAsync(new Project { Name = new string('a', 101) }); - result.Errors.Should().Contain(e => e.PropertyName == "Name" && e.ErrorMessage == "Name must not exceed 100 characters."); + Assert.Contains(result.Errors, e => e.PropertyName == "Name" && e.ErrorMessage == "Name must not exceed 100 characters."); } [Fact] @@ -38,7 +37,7 @@ public async Task Validate_ShouldPass_WhenNameIsExactly100Characters() { var result = await _validator.ValidateAsync(new Project { Name = new string('a', 100) }); - result.IsValid.Should().BeTrue(); + Assert.True(result.IsValid); } [Fact] @@ -46,7 +45,7 @@ public async Task Validate_ShouldFail_WhenDescriptionExceeds2000Characters() { var result = await _validator.ValidateAsync(new Project { Name = "Alpha", Description = new string('a', 2001) }); - result.Errors.Should().Contain(e => e.PropertyName == "Description" && e.ErrorMessage == "Description must not exceed 2000 characters."); + Assert.Contains(result.Errors, e => e.PropertyName == "Description" && e.ErrorMessage == "Description must not exceed 2000 characters."); } } @@ -59,7 +58,7 @@ public async Task Validate_ShouldPass_WhenTaskItemIdIsPositive() { var result = await _validator.ValidateAsync(new AssignProjectTaskDto { TaskItemId = 1 }); - result.IsValid.Should().BeTrue(); + Assert.True(result.IsValid); } [Theory] @@ -69,6 +68,6 @@ public async Task Validate_ShouldFail_WhenTaskItemIdIsNotPositive(int id) { var result = await _validator.ValidateAsync(new AssignProjectTaskDto { TaskItemId = id }); - result.Errors.Should().Contain(e => e.PropertyName == "TaskItemId"); + Assert.Contains(result.Errors, e => e.PropertyName == "TaskItemId"); } } diff --git a/TaskFlow.Api.Tests/Validators/TaskItemValidatorTests.cs b/TaskFlow.Api.Tests/Validators/TaskItemValidatorTests.cs index 70d0e025..ef850e16 100644 --- a/TaskFlow.Api.Tests/Validators/TaskItemValidatorTests.cs +++ b/TaskFlow.Api.Tests/Validators/TaskItemValidatorTests.cs @@ -1,4 +1,3 @@ -using FluentAssertions; using Microsoft.EntityFrameworkCore; using TaskFlow.Api.Data; using TaskFlow.Api.Models; @@ -31,8 +30,8 @@ public async Task Validate_ShouldPass_WhenTaskIsValid() var result = await _validator.ValidateAsync(task); // Assert - result.IsValid.Should().BeTrue(); - result.Errors.Should().BeEmpty(); + Assert.True(result.IsValid); + Assert.Empty(result.Errors); } [Fact] @@ -51,8 +50,8 @@ public async Task Validate_ShouldPass_WhenDescriptionIsNull() var result = await _validator.ValidateAsync(task); // Assert - result.IsValid.Should().BeTrue(); - result.Errors.Should().BeEmpty(); + Assert.True(result.IsValid); + Assert.Empty(result.Errors); } [Fact] @@ -71,8 +70,8 @@ public async Task Validate_ShouldPass_WhenTaskIsComplete() var result = await _validator.ValidateAsync(task); // Assert - result.IsValid.Should().BeTrue(); - result.Errors.Should().BeEmpty(); + Assert.True(result.IsValid); + Assert.Empty(result.Errors); } [Fact] @@ -91,10 +90,10 @@ public async Task Validate_ShouldFail_WhenTitleIsEmpty() var result = await _validator.ValidateAsync(task); // Assert - result.IsValid.Should().BeFalse(); - result.Errors.Should().HaveCount(1); - result.Errors[0].PropertyName.Should().Be("Title"); - result.Errors[0].ErrorMessage.Should().Be("Title is required."); + Assert.False(result.IsValid); + Assert.Single(result.Errors); + Assert.Equal("Title", result.Errors[0].PropertyName); + Assert.Equal("Title is required.", result.Errors[0].ErrorMessage); } [Fact] @@ -113,10 +112,10 @@ public async Task Validate_ShouldFail_WhenTitleExceedsMaxLength() var result = await _validator.ValidateAsync(task); // Assert - result.IsValid.Should().BeFalse(); - result.Errors.Should().HaveCount(1); - result.Errors[0].PropertyName.Should().Be("Title"); - result.Errors[0].ErrorMessage.Should().Be("Title must not exceed 100 characters."); + Assert.False(result.IsValid); + Assert.Single(result.Errors); + Assert.Equal("Title", result.Errors[0].PropertyName); + Assert.Equal("Title must not exceed 100 characters.", result.Errors[0].ErrorMessage); } [Fact] @@ -135,8 +134,8 @@ public async Task Validate_ShouldPass_WhenTitleIsExactly100Characters() var result = await _validator.ValidateAsync(task); // Assert - result.IsValid.Should().BeTrue(); - result.Errors.Should().BeEmpty(); + Assert.True(result.IsValid); + Assert.Empty(result.Errors); } [Fact] @@ -155,8 +154,8 @@ public async Task Validate_ShouldPass_WhenTitleIs1Character() var result = await _validator.ValidateAsync(task); // Assert - result.IsValid.Should().BeTrue(); - result.Errors.Should().BeEmpty(); + Assert.True(result.IsValid); + Assert.Empty(result.Errors); } [Fact] @@ -175,8 +174,8 @@ public async Task Validate_ShouldPass_WhenDescriptionIsEmpty() var result = await _validator.ValidateAsync(task); // Assert - result.IsValid.Should().BeTrue(); - result.Errors.Should().BeEmpty(); + Assert.True(result.IsValid); + Assert.Empty(result.Errors); } [Fact] @@ -195,8 +194,8 @@ public async Task Validate_ShouldPass_WhenDescriptionIsVeryLong() var result = await _validator.ValidateAsync(task); // Assert - result.IsValid.Should().BeTrue(); - result.Errors.Should().BeEmpty(); + Assert.True(result.IsValid); + Assert.Empty(result.Errors); } [Fact] @@ -215,8 +214,8 @@ public async Task Validate_ShouldPass_WhenStatusIsDraft() var result = await _validator.ValidateAsync(task); // Assert - result.IsValid.Should().BeTrue(); - result.Errors.Should().BeEmpty(); + Assert.True(result.IsValid); + Assert.Empty(result.Errors); } [Fact] @@ -235,8 +234,8 @@ public async Task Validate_ShouldPass_WhenStatusIsCompleted() var result = await _validator.ValidateAsync(task); // Assert - result.IsValid.Should().BeTrue(); - result.Errors.Should().BeEmpty(); + Assert.True(result.IsValid); + Assert.Empty(result.Errors); } [Fact] @@ -255,8 +254,8 @@ public async Task Validate_ShouldFail_WhenStatusIsOutOfRange() var result = await _validator.ValidateAsync(task); // Assert - result.IsValid.Should().BeFalse(); - result.Errors.Should().Contain(e => + Assert.False(result.IsValid); + Assert.Contains(result.Errors, e => e.PropertyName == "Status" && e.ErrorMessage == "Status must be a valid value (Draft, Todo, or Completed)."); } diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index ea62fe7a..cc1635fd 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -305,7 +305,7 @@ public class TaskItemsControllerV1Tests var result = await controller.Get(1); // Assert - result.Result.Should().BeOfType(); + Assert.IsType(result.Result); } } ``` diff --git a/docs/SERVICE_REGISTRATION_PATTERN.md b/docs/SERVICE_REGISTRATION_PATTERN.md index 7e142bbc..b335d7bd 100644 --- a/docs/SERVICE_REGISTRATION_PATTERN.md +++ b/docs/SERVICE_REGISTRATION_PATTERN.md @@ -150,7 +150,7 @@ public void AddCaching_ShouldRegisterCachingServices() var serviceProvider = services.BuildServiceProvider(); // Assert - serviceProvider.GetService().Should().NotBeNull(); + Assert.NotNull(serviceProvider.GetService()); } ``` @@ -296,8 +296,8 @@ public class ServiceCollectionExtensionsTests var serviceProvider = services.BuildServiceProvider(); // Assert - serviceProvider.GetService().Should().NotBeNull(); - serviceProvider.GetService().Should().NotBeNull(); + Assert.NotNull(serviceProvider.GetService()); + Assert.NotNull(serviceProvider.GetService()); } } ``` From dacab873cbc94497307e396ecf05ecfa8e931375 Mon Sep 17 00:00:00 2001 From: Neil Date: Wed, 21 Oct 2026 07:35:28 -0500 Subject: [PATCH 2/2] fix(tests): address important/minor validator findings for replace-fluentassertions-with-xunit --- .../V1/TaskItemsControllerV1Tests.cs | 4 +- .../JournalEntryRepositoryTests.cs | 52 +++++++++---------- .../JournalLogEntryRepositoryTests.cs | 12 ++--- .../JournalNoteRepositoryTests.cs | 4 +- .../Repositories/NoteRepositoryTests.cs | 2 +- .../Repositories/ProjectRepositoryTests.cs | 12 ++--- .../Repositories/TaskRepositoryTests.cs | 20 +++---- 7 files changed, 53 insertions(+), 53 deletions(-) diff --git a/TaskFlow.Api.Tests/Controllers/V1/TaskItemsControllerV1Tests.cs b/TaskFlow.Api.Tests/Controllers/V1/TaskItemsControllerV1Tests.cs index 0b91a754..dde4ae35 100644 --- a/TaskFlow.Api.Tests/Controllers/V1/TaskItemsControllerV1Tests.cs +++ b/TaskFlow.Api.Tests/Controllers/V1/TaskItemsControllerV1Tests.cs @@ -387,7 +387,7 @@ public async Task Create_ShouldLeaveTodaysTasksOnToday_WhenTaskCreatedForTomorro Assert.IsType(result.Result); var tomorrowEntry = await journalRepo.GetByDateAsync(today.AddDays(1)); Assert.Equal("Tomorrow task", Assert.Single(tomorrowEntry!.Todos.Select(t => t.Title))); - Assert.Single((await journalRepo.GetTodosAsync(todayEntry.Id)), t => t.Id == todayTask.Id); + Assert.Single(await journalRepo.GetTodosAsync(todayEntry.Id), t => t.Id == todayTask.Id); Assert.Equal(0, context.TaskItems.Single(t => t.Id == todayTask.Id).MoveCount); } @@ -424,7 +424,7 @@ public async Task Create_ShouldNotMoveOtherTasks_WhenJournalDateIsTomorrowAndTod var before = seeded.ToDictionary(t => t.Id, t => (t.CurrentJournalEntryId, t.MoveCount, t.FirstTaggedDate)); var eventsBefore = await context.TaskItemEvents.CountAsync(); - Assert.Null((await journalRepo.GetByDateAsync(tomorrow))); + Assert.Null(await journalRepo.GetByDateAsync(tomorrow)); var result = await controller.Create(new CreateTaskItemDto { diff --git a/TaskFlow.Api.Tests/Repositories/JournalEntryRepositoryTests.cs b/TaskFlow.Api.Tests/Repositories/JournalEntryRepositoryTests.cs index ae3e63ca..ff3e1779 100644 --- a/TaskFlow.Api.Tests/Repositories/JournalEntryRepositoryTests.cs +++ b/TaskFlow.Api.Tests/Repositories/JournalEntryRepositoryTests.cs @@ -41,7 +41,7 @@ public async Task AddAndGetByDate_ShouldPersistEntry() var found = await repo.GetByDateAsync(date); Assert.NotNull(found); - Assert.Equal("May 10", found!.Title); + Assert.Equal("May 10", found.Title); Assert.Equal(DateTime.UtcNow, found.CreatedAt, TimeSpan.FromSeconds(5)); } @@ -57,7 +57,7 @@ public async Task GetAll_ShouldReturnDescendingByDate() var all = (await repo.GetAllAsync()).ToList(); - Assert.Equal(2, all.Count()); + Assert.Equal(2, all.Count); Assert.Equal("Newer", all[0].Title); } @@ -74,12 +74,12 @@ public async Task AddTodoAndRemoveTodo_ShouldManageLinks() await repo.AddTodoAsync(entry.Id, task.Id); - Assert.True((await repo.TodoExistsAsync(entry.Id, task.Id))); - Assert.Single((await repo.GetTodosAsync(entry.Id)), t => t.Id == task.Id); + Assert.True(await repo.TodoExistsAsync(entry.Id, task.Id)); + Assert.Single(await repo.GetTodosAsync(entry.Id), t => t.Id == task.Id); await repo.RemoveTodoAsync(entry.Id, task.Id); - Assert.False((await repo.TodoExistsAsync(entry.Id, task.Id))); + Assert.False(await repo.TodoExistsAsync(entry.Id, task.Id)); Assert.Null(task.CurrentJournalEntryId); } @@ -122,8 +122,8 @@ public async Task AddTodoAsync_ShouldMoveTaskBetweenEntries_AndIncrementMoveCoun Assert.Equal(secondEntry.Id, task.CurrentJournalEntryId); Assert.Equal(firstEntry.Date, task.FirstTaggedDate); Assert.Equal(1, task.MoveCount); - Assert.False((await repo.TodoExistsAsync(firstEntry.Id, task.Id))); - Assert.True((await repo.TodoExistsAsync(secondEntry.Id, task.Id))); + Assert.False(await repo.TodoExistsAsync(firstEntry.Id, task.Id)); + Assert.True(await repo.TodoExistsAsync(secondEntry.Id, task.Id)); } [Fact] @@ -193,7 +193,7 @@ public async Task Delete_ShouldRemoveEntry_AndIgnoreMissing() await repo.DeleteAsync(entry.Id); await repo.DeleteAsync(999); - Assert.Null((await repo.GetByIdAsync(entry.Id))); + Assert.Null(await repo.GetByIdAsync(entry.Id)); } [Fact] @@ -208,9 +208,9 @@ public async Task AddAsync_ShouldNotMoveAnyTasks_WhenCreatingFutureOrPastEntry() var future = await repo.AddAsync(new JournalEntry { Title = "Future", Date = today.AddDays(1) }); var past = await repo.AddAsync(new JournalEntry { Title = "Past", Date = today.AddDays(-5) }); - Assert.Single((await repo.GetTodosAsync(source.Id)), t => t.Id == task.Id); - Assert.Empty((await repo.GetTodosAsync(future.Id))); - Assert.Empty((await repo.GetTodosAsync(past.Id))); + Assert.Single(await repo.GetTodosAsync(source.Id), t => t.Id == task.Id); + Assert.Empty(await repo.GetTodosAsync(future.Id)); + Assert.Empty(await repo.GetTodosAsync(past.Id)); Assert.Equal(source.Id, task.CurrentJournalEntryId); Assert.Equal(0, task.MoveCount); Assert.Empty(context.TaskItemEvents); @@ -229,8 +229,8 @@ public async Task CarryForwardIncompleteTasksAsync_ShouldLeaveCompletedTasksInPl var moved = await repo.CarryForwardIncompleteTasksAsync(target.Id, today); Assert.Equal(new[] { open.Id }, moved); - Assert.Single((await repo.GetTodosAsync(target.Id)), t => t.Id == open.Id); - Assert.Single((await repo.GetTodosAsync(source.Id)), t => t.Id == done.Id); + Assert.Single(await repo.GetTodosAsync(target.Id), t => t.Id == open.Id); + Assert.Single(await repo.GetTodosAsync(source.Id), t => t.Id == done.Id); Assert.Equal(source.Id, done.CurrentJournalEntryId); Assert.Equal(0, done.MoveCount); } @@ -249,8 +249,8 @@ public async Task CarryForwardIncompleteTasksAsync_ShouldMoveTasksFromAllEarlier Assert.Equal(new[] { olderTask.Id, newerTask.Id }, moved); Assert.Equal(new[] { olderTask.Id, newerTask.Id }.Order(), (await repo.GetTodosAsync(target.Id)).Select(t => t.Id).Order()); - Assert.Empty((await repo.GetTodosAsync(older.Id))); - Assert.Empty((await repo.GetTodosAsync(newer.Id))); + Assert.Empty(await repo.GetTodosAsync(older.Id)); + Assert.Empty(await repo.GetTodosAsync(newer.Id)); Assert.Equal(older.Date, olderTask.FirstTaggedDate); Assert.Equal(newer.Date, newerTask.FirstTaggedDate); } @@ -274,7 +274,7 @@ public async Task AddTodoAsync_AndRemoveTodoAsync_ShouldWriteTaskHistoryEvents() .OrderBy(e => e.OccurredAtUtc) .ToListAsync(); - Assert.Equal(2, events.Count()); + Assert.Equal(2, events.Count); Assert.Equal("AssignedToJournalDay", events[0].EventType); Assert.Equal(entry.Id, events[0].ToJournalEntryId); Assert.Equal("RemovedFromJournalDay", events[1].EventType); @@ -353,7 +353,7 @@ public async Task GetByIdAsync_ShouldIncludeChildTaskItems_OnTodos() var found = await repo.GetByIdAsync(entry.Id); Assert.NotNull(found); - var parentTodo = found!.Todos.First(t => t.Id == parent.Id); + var parentTodo = found.Todos.First(t => t.Id == parent.Id); Assert.Single(parentTodo.ChildTaskItems, c => c.Id == child.Id); } @@ -378,7 +378,7 @@ public async Task GetByDateAsync_ShouldIncludeChildTaskItems_OnTodos() var found = await repo.GetByDateAsync(date); Assert.NotNull(found); - var parentTodo = found!.Todos.First(t => t.Id == parent.Id); + var parentTodo = found.Todos.First(t => t.Id == parent.Id); Assert.Single(parentTodo.ChildTaskItems, c => c.Id == child.Id); } @@ -485,7 +485,7 @@ public async Task GetTodosAsync_ShouldIncludeCurrentJournalEntry_OnChildTaskItem var parentTodo = todos.First(t => t.Id == parent.Id); var childItem = parentTodo.ChildTaskItems.First(c => c.Id == child.Id); Assert.NotNull(childItem.CurrentJournalEntry); - Assert.Equal(secondEntry.Id, childItem.CurrentJournalEntry!.Id); + Assert.Equal(secondEntry.Id, childItem.CurrentJournalEntry.Id); } [Fact] @@ -510,7 +510,7 @@ public async Task CarryForwardIncompleteTasksAsync_ShouldSetCountersAndEvents_Fo Assert.Equal(3, taskB.MoveCount); Assert.Equal(today.AddDays(-9), taskB.FirstTaggedDate); var events = context.TaskItemEvents.ToList(); - Assert.Equal(2, events.Count()); + Assert.Equal(2, events.Count); Assert.All(events, e => Assert.True(e.EventType == "ReassignedToJournalDay" && e.ChangeSummary == "Automatically moved forward to the next journal day." && e.ToJournalEntryId == target.Id && e.ToJournalDate == today)); @@ -555,7 +555,7 @@ public async Task CarryForwardIncompleteTasksAsync_ShouldNotTouchPlannedDayBetwe Assert.Equal(new[] { taskA.Id }, moved); Assert.Equal(target.Id, taskA.CurrentJournalEntryId); - Assert.Empty((await repo.GetTodosAsync(a.Id))); + Assert.Empty(await repo.GetTodosAsync(a.Id)); Assert.Equal(0, taskFuture.MoveCount); Assert.NotEqual(target.Id, taskFuture.CurrentJournalEntryId); Assert.Null(orphan.CurrentJournalEntryId); @@ -588,7 +588,7 @@ public async Task CarryForwardIncompleteTasksAsync_ShouldBeIdempotent_WhenRunTwi var target = await repo.AddAsync(new JournalEntry { Title = "Today", Date = today }); Assert.Equal(new[] { task.Id }, (await repo.CarryForwardIncompleteTasksAsync(target.Id, today))); - Assert.Empty((await repo.CarryForwardIncompleteTasksAsync(target.Id, today))); + Assert.Empty(await repo.CarryForwardIncompleteTasksAsync(target.Id, today)); Assert.Equal(1, task.MoveCount); Assert.Single(context.TaskItemEvents); @@ -610,7 +610,7 @@ public async Task CarryForwardIncompleteTasksAsync_ShouldNotPullBackTaskComplete await context.SaveChangesAsync(); await repo.RemoveTodoAsync(target.Id, removedLater.Id); - Assert.Empty((await repo.CarryForwardIncompleteTasksAsync(target.Id, today))); + Assert.Empty(await repo.CarryForwardIncompleteTasksAsync(target.Id, today)); Assert.Equal(target.Id, completedLater.CurrentJournalEntryId); Assert.Null(removedLater.CurrentJournalEntryId); Assert.Equal(1, completedLater.MoveCount); @@ -645,7 +645,7 @@ public async Task CarryForwardIncompleteTasksAsync_ShouldReturnZero_WhenTargetIs var (_, task) = await SeedLinkedTaskAsync(context, today.AddDays(-1)); var tomorrow = await repo.AddAsync(new JournalEntry { Title = "Tomorrow", Date = today.AddDays(1) }); - Assert.Empty((await repo.CarryForwardIncompleteTasksAsync(tomorrow.Id, today))); + Assert.Empty(await repo.CarryForwardIncompleteTasksAsync(tomorrow.Id, today)); Assert.Equal(0, task.MoveCount); Assert.Empty(context.TaskItemEvents); @@ -657,7 +657,7 @@ public async Task CarryForwardIncompleteTasksAsync_ShouldReturnZero_WhenEntryMis using var context = CreateInMemoryContext(); var repo = new JournalEntryRepository(context); - Assert.Empty((await repo.CarryForwardIncompleteTasksAsync(999, new DateOnly(2026, 10, 13)))); + Assert.Empty(await repo.CarryForwardIncompleteTasksAsync(999, new DateOnly(2026, 10, 13))); } [Fact] @@ -672,7 +672,7 @@ public async Task CarryForwardIncompleteTasksAsync_ShouldReturnEmpty_WhenNoIncom await context.SaveChangesAsync(); var target = await repo.AddAsync(new JournalEntry { Title = "Today", Date = today }); - Assert.Empty((await repo.CarryForwardIncompleteTasksAsync(target.Id, today))); + Assert.Empty(await repo.CarryForwardIncompleteTasksAsync(target.Id, today)); Assert.Empty(context.TaskItemEvents); } diff --git a/TaskFlow.Api.Tests/Repositories/JournalLogEntryRepositoryTests.cs b/TaskFlow.Api.Tests/Repositories/JournalLogEntryRepositoryTests.cs index 4140241f..3c12f2fe 100644 --- a/TaskFlow.Api.Tests/Repositories/JournalLogEntryRepositoryTests.cs +++ b/TaskFlow.Api.Tests/Repositories/JournalLogEntryRepositoryTests.cs @@ -43,7 +43,7 @@ public async Task AddAndGetAll_ShouldReturnLogsOrdered() var logs = (await repo.GetAllByEntryIdAsync(entry.Id)).ToList(); - Assert.Equal(2, logs.Count()); + Assert.Equal(2, logs.Count); Assert.Equal("A", logs[0].Content); Assert.Equal("B", logs[1].Content); } @@ -64,12 +64,12 @@ public async Task UpdateAndDelete_ShouldSetUpdatedAtAndRemove() var fetched = await repo.GetByIdAsync(entry.Id, log.Id); Assert.NotNull(fetched); - Assert.NotNull(fetched!.UpdatedAt); + Assert.NotNull(fetched.UpdatedAt); await repo.DeleteAsync(entry.Id, log.Id); await repo.DeleteAsync(entry.Id, 999); - Assert.Null((await repo.GetByIdAsync(entry.Id, log.Id))); + Assert.Null(await repo.GetByIdAsync(entry.Id, log.Id)); } [Fact] @@ -97,8 +97,8 @@ public async Task GetByIdAsync_ShouldIncludeTaskItem() var fetched = await repo.GetByIdAsync(entry.Id, log.Id); Assert.NotNull(fetched); - Assert.NotNull(fetched!.TaskItem); - Assert.Equal("Linked Task", fetched.TaskItem!.Title); + Assert.NotNull(fetched.TaskItem); + Assert.Equal("Linked Task", fetched.TaskItem.Title); } } @@ -131,7 +131,7 @@ public async Task DeleteTask_ShouldNullTaskItemId_AndPreserveSnapshot() var fetched = await repo.GetByIdAsync(entry.Id, log.Id); Assert.NotNull(fetched); - Assert.Null(fetched!.TaskItemId); + Assert.Null(fetched.TaskItemId); Assert.Equal("Will Be Deleted", fetched.LinkedTaskTitleSnapshot); } } diff --git a/TaskFlow.Api.Tests/Repositories/JournalNoteRepositoryTests.cs b/TaskFlow.Api.Tests/Repositories/JournalNoteRepositoryTests.cs index 072360d4..c7b446d7 100644 --- a/TaskFlow.Api.Tests/Repositories/JournalNoteRepositoryTests.cs +++ b/TaskFlow.Api.Tests/Repositories/JournalNoteRepositoryTests.cs @@ -30,7 +30,7 @@ public async Task GetAllByEntryIdAsync_ShouldReturnNotesOrderedByCreatedAt() var notes = (await repo.GetAllByEntryIdAsync(entry.Id)).ToList(); - Assert.Equal(2, notes.Count()); + Assert.Equal(2, notes.Count); Assert.Equal("First", notes[0].Content); Assert.Equal("Second", notes[1].Content); } @@ -115,7 +115,7 @@ public async Task AddAsync_ShouldPersistNoteWithId() var repo = new JournalNoteRepository(context); var note = await repo.AddAsync(new JournalNote { JournalEntryId = entry.Id, Content = "New note" }); - Assert.True(note.Id > 0); + Assert.InRange(note.Id, 1, int.MaxValue); Assert.NotEqual(default, note.CreatedAt); Assert.Equal("New note", note.Content); } diff --git a/TaskFlow.Api.Tests/Repositories/NoteRepositoryTests.cs b/TaskFlow.Api.Tests/Repositories/NoteRepositoryTests.cs index 9d8c1ee6..23e8fa80 100644 --- a/TaskFlow.Api.Tests/Repositories/NoteRepositoryTests.cs +++ b/TaskFlow.Api.Tests/Repositories/NoteRepositoryTests.cs @@ -111,7 +111,7 @@ public async Task AddAsync_ShouldAddNoteAndReturnIt() var result = await repo.AddAsync(note); - Assert.True(result.Id > 0); + Assert.InRange(result.Id, 1, int.MaxValue); Assert.Equal("New note", result.Content); Assert.Equal(1, result.TaskItemId); Assert.Equal(DateTime.UtcNow, result.CreatedAt, TimeSpan.FromSeconds(5)); diff --git a/TaskFlow.Api.Tests/Repositories/ProjectRepositoryTests.cs b/TaskFlow.Api.Tests/Repositories/ProjectRepositoryTests.cs index bccbb42f..b75f19f6 100644 --- a/TaskFlow.Api.Tests/Repositories/ProjectRepositoryTests.cs +++ b/TaskFlow.Api.Tests/Repositories/ProjectRepositoryTests.cs @@ -67,9 +67,9 @@ public async Task NameExistsAsync_ShouldHonorExcludeId() var repo = new ProjectRepository(context); var p = await repo.AddAsync(new Project { Name = "Alpha" }); - Assert.True((await repo.NameExistsAsync("Alpha"))); - Assert.False((await repo.NameExistsAsync("Alpha", p.Id))); - Assert.False((await repo.NameExistsAsync("Other"))); + Assert.True(await repo.NameExistsAsync("Alpha")); + Assert.False(await repo.NameExistsAsync("Alpha", p.Id)); + Assert.False(await repo.NameExistsAsync("Other")); } [Fact] @@ -160,7 +160,7 @@ public async Task UnassignTaskAsync_ShouldClearProject_AndRecordEvent() context.TaskItems.Add(task); await context.SaveChangesAsync(); - Assert.True((await repo.UnassignTaskAsync(p.Id, task.Id))); + Assert.True(await repo.UnassignTaskAsync(p.Id, task.Id)); Assert.Null((await context.TaskItems.AsNoTracking().SingleAsync()).ProjectId); Assert.Equal("Removed from project 'Alpha'.", (await context.TaskItemEvents.SingleAsync()).ChangeSummary); @@ -176,7 +176,7 @@ public async Task UnassignTaskAsync_ShouldReturnFalse_WhenTaskNotInProject() context.TaskItems.Add(task); await context.SaveChangesAsync(); - Assert.False((await repo.UnassignTaskAsync(p.Id, task.Id))); + Assert.False(await repo.UnassignTaskAsync(p.Id, task.Id)); } [Fact] @@ -210,7 +210,7 @@ public async Task Name_ShouldBeUniqueCaseInsensitively_OnSqlite() var repo = new ProjectRepository(context); await repo.AddAsync(new Project { Name = "Alpha" }); - Assert.True((await repo.NameExistsAsync("alpha"))); + Assert.True(await repo.NameExistsAsync("alpha")); var act = () => repo.AddAsync(new Project { Name = "ALPHA" }); await Assert.ThrowsAsync(act); diff --git a/TaskFlow.Api.Tests/Repositories/TaskRepositoryTests.cs b/TaskFlow.Api.Tests/Repositories/TaskRepositoryTests.cs index ceb70e65..f72e5bfa 100644 --- a/TaskFlow.Api.Tests/Repositories/TaskRepositoryTests.cs +++ b/TaskFlow.Api.Tests/Repositories/TaskRepositoryTests.cs @@ -75,7 +75,7 @@ public async Task GetByIdAsync_ShouldReturnTask_WhenTaskExists() // Assert Assert.NotNull(result); - Assert.Equal(Status.Todo, result!.Status); + Assert.Equal(Status.Todo, result.Status); Assert.Equal("Task 1", result.Title); } @@ -106,7 +106,7 @@ public async Task AddAsync_ShouldAddTaskAndReturnIt() // Assert Assert.NotNull(result); - Assert.True(result.Id > 0); + Assert.InRange(result.Id, 1, int.MaxValue); Assert.Equal("New Task", result.Title); var savedTask = await context.TaskItems.FindAsync(result.Id); @@ -135,7 +135,7 @@ public async Task AddAsync_ShouldHandleTaskWithNullDescription() var savedTask = await context.TaskItems.FindAsync(result.Id); Assert.NotNull(savedTask); - Assert.Null(savedTask!.Description); + Assert.Null(savedTask.Description); } [Fact] @@ -155,7 +155,7 @@ public async Task AddAsync_ShouldHandleCompletedTask() var savedTask = await context.TaskItems.FindAsync(result.Id); Assert.NotNull(savedTask); - Assert.True(savedTask!.IsComplete); + Assert.True(savedTask.IsComplete); } [Fact] @@ -179,7 +179,7 @@ public async Task UpdateAsync_ShouldUpdateExistingTask() // Assert var updatedTask = await context.TaskItems.FindAsync(1); Assert.NotNull(updatedTask); - Assert.Equal("Updated", updatedTask!.Title); + Assert.Equal("Updated", updatedTask.Title); Assert.Equal("Updated Description", updatedTask.Description); Assert.True(updatedTask.IsComplete); } @@ -203,7 +203,7 @@ public async Task UpdateAsync_ShouldHandlePartialUpdate() // Assert var updatedTask = await context.TaskItems.FindAsync(1); Assert.NotNull(updatedTask); - Assert.Equal("Original", updatedTask!.Title); + Assert.Equal("Original", updatedTask.Title); Assert.Equal("Description", updatedTask.Description); Assert.True(updatedTask.IsComplete); } @@ -220,7 +220,7 @@ public async Task UpdateAsync_ShouldWriteTitleAndPriorityHistoryEvents_WhenValue var tracked = await context.TaskItems.FindAsync(1); Assert.NotNull(tracked); - tracked!.Title = "Updated"; + tracked.Title = "Updated"; tracked.Priority = Priority.High; await repository.UpdateAsync(tracked); @@ -246,14 +246,14 @@ public async Task UpdateAsync_ShouldWriteCompletedAndReopenedEvents_WhenCompleti var tracked = await context.TaskItems.FindAsync(1); Assert.NotNull(tracked); - tracked!.IsComplete = true; + tracked.IsComplete = true; tracked.Status = Status.Completed; await repository.UpdateAsync(tracked); context.ChangeTracker.Clear(); tracked = await context.TaskItems.FindAsync(1); Assert.NotNull(tracked); - tracked!.IsComplete = false; + tracked.IsComplete = false; tracked.Status = Status.Todo; await repository.UpdateAsync(tracked); @@ -287,7 +287,7 @@ public async Task UpdateAsync_ShouldWriteDescriptionAndDueDateHistoryEvents_When var tracked = await context.TaskItems.FindAsync(1); Assert.NotNull(tracked); - tracked!.Description = "Updated description"; + tracked.Description = "Updated description"; tracked.DueDate = dueDate.AddDays(3); await repository.UpdateAsync(tracked);