diff --git a/backend/src/Taskdeck.Application/Services/BoardService.cs b/backend/src/Taskdeck.Application/Services/BoardService.cs index 40fdcb838..9cad6347e 100644 --- a/backend/src/Taskdeck.Application/Services/BoardService.cs +++ b/backend/src/Taskdeck.Application/Services/BoardService.cs @@ -100,8 +100,12 @@ public async Task> GetBoardDetailAsync(Guid id, Guid acti var detail = await GetBoardDetailAsync(id, cancellationToken); if (!detail.IsSuccess) return detail; - var writable = _authorizationService is null ? null : await _authorizationService.CanWriteBoardAsync(actingUserId, id); - return Result.Success(detail.Value with { CanWrite = writable is { IsSuccess: true, Value: true } }); + var writable = _authorizationService is null + || (await _authorizationService.CanWriteBoardAsync(actingUserId, id)) is { IsSuccess: true, Value: true }; + // No authorization service configured (CLI / unauthenticated composition) means no + // authorization is being enforced at all, so the caller can write — the same + // convention ListBoardsPaginatedAsync and EnsureBoardPermissionAsync follow. + return Result.Success(detail.Value with { CanWrite = writable }); } /// diff --git a/backend/tests/Taskdeck.Application.Tests/Services/BoardServiceTests.cs b/backend/tests/Taskdeck.Application.Tests/Services/BoardServiceTests.cs index 1991e966d..4e40378da 100644 --- a/backend/tests/Taskdeck.Application.Tests/Services/BoardServiceTests.cs +++ b/backend/tests/Taskdeck.Application.Tests/Services/BoardServiceTests.cs @@ -588,6 +588,44 @@ public async Task CreateBoardAsync_ShouldStampCanWriteTrue_ForTheCreatingOwner() result.IsSuccess.Should().BeTrue(); result.Value.CanWrite.Should().BeTrue(); } + [Fact] + public async Task GetBoardDetailAsync_ShouldStampCanWriteTrue_WhenNoAuthorizationService() + { + // Null authorization (CLI / unauthenticated composition) means no enforcement, + // so detail must agree with the list stamp: the caller can write. + var board = TestDataBuilder.CreateBoard("Detail Board"); + _boardRepoMock.Setup(r => r.GetByIdWithDetailsAsync(board.Id, default)) + .ReturnsAsync(board); + + var result = await _service.GetBoardDetailAsync(board.Id, Guid.NewGuid()); + + result.IsSuccess.Should().BeTrue(); + result.Value.CanWrite.Should().BeTrue(); + } + + [Fact] + public async Task ListBoardsPaginatedAsync_ShouldStampCanWriteTrue_WhenNoAuthorizationService() + { + // Parity lock for the detail stamp above: with no authorization service every + // visible board is writable. + var actingUserId = Guid.NewGuid(); + var boards = new[] + { + new Board("Board A", "desc", actingUserId), + new Board("Board B", "desc", actingUserId) + }; + var boardIds = boards.Select(b => b.Id).ToList(); + _boardRepoMock.Setup(r => r.SearchIdsAsync(null, false, default)) + .ReturnsAsync(boardIds); + _boardRepoMock.Setup(r => r.GetByIdsAsync(It.IsAny>(), default)) + .ReturnsAsync(boards); + + var result = await _service.ListBoardsPaginatedAsync(actingUserId); + + result.IsSuccess.Should().BeTrue(); + result.Value.Items.Should().HaveCount(2); + result.Value.Items.Should().OnlyContain(b => b.CanWrite); + } #endregion }