From cca20a9965d84e70a3165d4341aaafbfc53cbb48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Ty=C5=84ski?= Date: Sun, 4 Oct 2026 03:49:51 +0200 Subject: [PATCH] test(backend): cover step 17 modules and extract stubs Add tests for partnership workflow, middleman adaptation and idea notifications; extract reusable test stubs into separate files; add architecture guard forbidding object-valued dictionaries in data contracts. --- .../NoObjectValuedDictionariesTests.cs | 60 ++++++++++++++++++ .../ApplicationsControllerTests.cs | 24 +------ .../Controllers/IdeasControllerTests.cs | 54 ++++++++++++++++ .../Controllers/InnovationsControllerTests.cs | 39 ++---------- .../Controllers/SubmissionsControllerTests.cs | 42 ++----------- .../Domain/PartnershipWorkflowTests.cs | 25 ++++++++ .../Infrastructure/MiddlemanAssistantTests.cs | 62 +++++++++++++++++++ .../Stubs/StubApplicationService.cs | 27 ++++++++ .../Stubs/StubIdeaAssistant.cs | 14 +++++ .../TarninaHub.Tests/Stubs/StubIdeaService.cs | 3 + .../Stubs/StubInnovationLookup.cs | 34 ++++++++++ .../Stubs/StubInnovationService.cs | 38 ++++++++++++ .../Stubs/StubNotificationService.cs | 40 ++++++++++++ .../Stubs/StubSubmissionService.cs | 41 ++++++++++++ .../Stubs/StubTestFeedbackService.cs | 18 ++++++ 15 files changed, 427 insertions(+), 94 deletions(-) create mode 100644 tests/TarninaHub.Tests/Architecture/NoObjectValuedDictionariesTests.cs create mode 100644 tests/TarninaHub.Tests/Controllers/IdeasControllerTests.cs create mode 100644 tests/TarninaHub.Tests/Domain/PartnershipWorkflowTests.cs create mode 100644 tests/TarninaHub.Tests/Infrastructure/MiddlemanAssistantTests.cs create mode 100644 tests/TarninaHub.Tests/Stubs/StubApplicationService.cs create mode 100644 tests/TarninaHub.Tests/Stubs/StubIdeaAssistant.cs create mode 100644 tests/TarninaHub.Tests/Stubs/StubInnovationLookup.cs create mode 100644 tests/TarninaHub.Tests/Stubs/StubInnovationService.cs create mode 100644 tests/TarninaHub.Tests/Stubs/StubNotificationService.cs create mode 100644 tests/TarninaHub.Tests/Stubs/StubSubmissionService.cs create mode 100644 tests/TarninaHub.Tests/Stubs/StubTestFeedbackService.cs diff --git a/tests/TarninaHub.Tests/Architecture/NoObjectValuedDictionariesTests.cs b/tests/TarninaHub.Tests/Architecture/NoObjectValuedDictionariesTests.cs new file mode 100644 index 0000000..bc75bcb --- /dev/null +++ b/tests/TarninaHub.Tests/Architecture/NoObjectValuedDictionariesTests.cs @@ -0,0 +1,60 @@ +using System.Text.RegularExpressions; + +namespace TarninaHub.Tests.Architecture; + +/// +/// Guarantees that data contracts never expose object-valued dictionaries. +/// Such dictionaries break CBOR deserialization of SurrealDB rows and force +/// lossy conversions, so only string-valued maps are allowed. +/// +public class NoObjectValuedDictionariesTests +{ + private static readonly Regex Forbidden = new(@"Dictionary(); + + foreach (var directory in new[] + { + Path.Combine(root, "backend", "Application", "Dtos"), + Path.Combine(root, "backend", "Application", "Abstractions"), + Path.Combine(root, "backend", "Domain"), + }) + { + files.AddRange(Directory.GetFiles(directory, "*.cs", SearchOption.AllDirectories)); + } + + files.AddRange(Directory.GetFiles( + Path.Combine(root, "backend", "Infrastructure", "Persistence"), + "*Row.cs", + SearchOption.AllDirectories)); + + var offenders = files + .Where(file => Forbidden.IsMatch(File.ReadAllText(file))) + .Select(file => Path.GetRelativePath(root, file)) + .OrderBy(path => path, StringComparer.Ordinal) + .ToList(); + + Assert.True( + offenders.Count == 0, + "Object-valued dictionaries are forbidden in data contracts. " + + "Use string-valued maps instead:\n" + string.Join(Environment.NewLine, offenders)); + } + + private static string FindRepositoryRoot() + { + var directory = new DirectoryInfo(AppContext.BaseDirectory); + + while (directory is not null && !File.Exists(Path.Combine(directory.FullName, "TarninaHub.slnx"))) + { + directory = directory.Parent; + } + + Assert.NotNull(directory); + + return directory!.FullName; + } +} diff --git a/tests/TarninaHub.Tests/Controllers/ApplicationsControllerTests.cs b/tests/TarninaHub.Tests/Controllers/ApplicationsControllerTests.cs index 021ff9b..c8f4fcc 100644 --- a/tests/TarninaHub.Tests/Controllers/ApplicationsControllerTests.cs +++ b/tests/TarninaHub.Tests/Controllers/ApplicationsControllerTests.cs @@ -49,7 +49,7 @@ public class ApplicationsControllerTests "application:new", "grant_call:x", "idea:y", - new Dictionary(), + new Dictionary(), "draft", DateTimeOffset.UtcNow); var service = new StubApplicationService( @@ -62,26 +62,4 @@ public class ApplicationsControllerTests Assert.IsType(result.Result); } - - private sealed class StubApplicationService : IApplicationService - { - private readonly CreateApplicationResult _createResult; - - public StubApplicationService(CreateApplicationResult createResult) - { - _createResult = createResult; - } - - public Task CreateAsync( - string userId, - CreateApplicationRequest request, - CancellationToken cancellationToken) => Task.FromResult(_createResult); - - public Task GetAsync(string applicationId, CancellationToken cancellationToken) => - Task.FromResult(_createResult.Application); - - public Task> ListForAuthorAsync( - string authorId, - CancellationToken cancellationToken) => Task.FromResult>([]); - } } diff --git a/tests/TarninaHub.Tests/Controllers/IdeasControllerTests.cs b/tests/TarninaHub.Tests/Controllers/IdeasControllerTests.cs new file mode 100644 index 0000000..6edd74e --- /dev/null +++ b/tests/TarninaHub.Tests/Controllers/IdeasControllerTests.cs @@ -0,0 +1,54 @@ +using System.Security.Claims; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using TarninaHub.Application.Abstractions; +using TarninaHub.Application.Dtos; +using TarninaHub.Controllers; +using TarninaHub.Tests.Stubs; + +namespace TarninaHub.Tests.Controllers; + +public class IdeasControllerTests +{ + private static IdeasController CreateController(IIdeaService ideaService, INotificationService notifications) + { + var controller = new IdeasController( + ideaService, + new StubIdeaAssistant(), + notifications); + var identity = new ClaimsIdentity( + [new Claim("sub", "user:tester"), new Claim("role", "user")], + authenticationType: "test"); + + controller.ControllerContext = new ControllerContext + { + HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal(identity) }, + }; + + return controller; + } + + [Fact] + public async Task Create_NotifiesStaffAndReturnsCreated() + { + var ideaService = new StubIdeaService { Idea = SampleIdea() }; + var notifications = new StubNotificationService(); + var controller = CreateController(ideaService, notifications); + + var result = await controller.Create( + new CreateIdeaRequest("Nowy pomysł", "Opis nowego pomysłu mieszkańca.", null), + CancellationToken.None); + + Assert.IsType(result.Result); + Assert.Equal(1, notifications.StaffNotifications); + } + + private static IdeaResponse SampleIdea() => new( + "idea:new", + "Nowy pomysł", + "Opis nowego pomysłu mieszkańca.", + "user:tester", + "draft", + null, + DateTimeOffset.UtcNow); +} diff --git a/tests/TarninaHub.Tests/Controllers/InnovationsControllerTests.cs b/tests/TarninaHub.Tests/Controllers/InnovationsControllerTests.cs index ad38b8a..51a1d84 100644 --- a/tests/TarninaHub.Tests/Controllers/InnovationsControllerTests.cs +++ b/tests/TarninaHub.Tests/Controllers/InnovationsControllerTests.cs @@ -5,6 +5,7 @@ using TarninaHub.Application.Abstractions; using TarninaHub.Application.Dtos; using TarninaHub.Controllers; using TarninaHub.Tests.Matchmaking; +using TarninaHub.Tests.Stubs; namespace TarninaHub.Tests.Controllers; @@ -12,7 +13,10 @@ public class InnovationsControllerTests { private static InnovationsController CreateController(IInnovationService service) { - var controller = new InnovationsController(service, new StubMatchmakingService()); + var controller = new InnovationsController( + service, + new StubMatchmakingService(), + new StubTestFeedbackService()); var identity = new ClaimsIdentity( [new Claim("sub", "user:tester"), new Claim("role", "admin")], authenticationType: "test"); @@ -55,37 +59,4 @@ public class InnovationsControllerTests Assert.IsType(result.Result); } - - private sealed class StubInnovationService : IInnovationService - { - public Task> ListAsync( - string? challengeId, - string? stage, - string? query, - bool onlyPublished, - CancellationToken cancellationToken) => Task.FromResult>([]); - - public Task GetAsync(string innovationId, CancellationToken cancellationToken) => - Task.FromResult(null); - - public Task CreateAsync( - InnovationRequest request, - CancellationToken cancellationToken) => Task.FromResult(new InnovationResponse( - "innovation:new", - request.Title, - request.Summary ?? string.Empty, - request.Description ?? string.Empty, - request.ChallengeId, - request.Stage, - request.Cost, - request.Contact, - request.Media ?? [], - request.Published, - DateTimeOffset.UtcNow)); - - public Task UpdateAsync( - string innovationId, - InnovationRequest request, - CancellationToken cancellationToken) => Task.FromResult(null); - } } diff --git a/tests/TarninaHub.Tests/Controllers/SubmissionsControllerTests.cs b/tests/TarninaHub.Tests/Controllers/SubmissionsControllerTests.cs index 2e6f908..5b1bbb2 100644 --- a/tests/TarninaHub.Tests/Controllers/SubmissionsControllerTests.cs +++ b/tests/TarninaHub.Tests/Controllers/SubmissionsControllerTests.cs @@ -5,6 +5,7 @@ using TarninaHub.Application.Abstractions; using TarninaHub.Application.Dtos; using TarninaHub.Controllers; using TarninaHub.Tests.Matchmaking; +using TarninaHub.Tests.Stubs; namespace TarninaHub.Tests.Controllers; @@ -12,7 +13,10 @@ public class SubmissionsControllerTests { private static SubmissionsController CreateController(ISubmissionService service, string role = "operator") { - var controller = new SubmissionsController(service, new StubMatchmakingService()); + var controller = new SubmissionsController( + service, + new StubMatchmakingService(), + new StubNotificationService()); var identity = new ClaimsIdentity( [new Claim("sub", "user:tester"), new Claim("role", role)], authenticationType: "test"); @@ -81,40 +85,4 @@ public class SubmissionsControllerTests Assert.IsType(result.Result); } - - private sealed class StubSubmissionService : ISubmissionService - { - public SubmissionResponse? Current { get; set; } - - public SubmissionResponse? UpdateResult { get; set; } - - public string? LastRequestedStatus { get; private set; } - - public Task CreateAsync( - string authorId, - CreateSubmissionRequest request, - CancellationToken cancellationToken) => Task.FromResult(SampleSubmission("new")); - - public Task> ListAsync( - string viewerId, - bool isStaff, - string? status, - int page, - int pageSize, - CancellationToken cancellationToken) => Task.FromResult( - new PagedResponse([], page, pageSize, 0)); - - public Task GetAsync(string submissionId, CancellationToken cancellationToken) => - Task.FromResult(Current); - - public Task UpdateStatusAsync( - string submissionId, - string newStatus, - CancellationToken cancellationToken) - { - LastRequestedStatus = newStatus; - - return Task.FromResult(UpdateResult); - } - } } diff --git a/tests/TarninaHub.Tests/Domain/PartnershipWorkflowTests.cs b/tests/TarninaHub.Tests/Domain/PartnershipWorkflowTests.cs new file mode 100644 index 0000000..fb8fb02 --- /dev/null +++ b/tests/TarninaHub.Tests/Domain/PartnershipWorkflowTests.cs @@ -0,0 +1,25 @@ +using TarninaHub.Domain.Enums; +using TarninaHub.Domain.Rules; + +namespace TarninaHub.Tests.Domain; + +public class PartnershipWorkflowTests +{ + [Theory] + [InlineData(PartnershipStatus.Proposed, PartnershipStatus.Active, true)] + [InlineData(PartnershipStatus.Proposed, PartnershipStatus.Closed, true)] + [InlineData(PartnershipStatus.Active, PartnershipStatus.Closed, true)] + [InlineData(PartnershipStatus.Closed, PartnershipStatus.Active, false)] + [InlineData(PartnershipStatus.Active, PartnershipStatus.Proposed, false)] + public void CanTransition_EnforcesLifecycle(PartnershipStatus from, PartnershipStatus to, bool expected) + { + Assert.Equal(expected, PartnershipWorkflow.CanTransition(from, to)); + } + + [Fact] + public void Closed_IsTerminal() + { + Assert.True(PartnershipWorkflow.IsTerminal(PartnershipStatus.Closed)); + Assert.False(PartnershipWorkflow.IsTerminal(PartnershipStatus.Proposed)); + } +} diff --git a/tests/TarninaHub.Tests/Infrastructure/MiddlemanAssistantTests.cs b/tests/TarninaHub.Tests/Infrastructure/MiddlemanAssistantTests.cs new file mode 100644 index 0000000..1cdfd52 --- /dev/null +++ b/tests/TarninaHub.Tests/Infrastructure/MiddlemanAssistantTests.cs @@ -0,0 +1,62 @@ +using TarninaHub.Application.Abstractions; +using TarninaHub.Application.Dtos; +using TarninaHub.Application.Matchmaking; +using TarninaHub.Infrastructure.Assist; +using TarninaHub.Infrastructure.Llm; +using TarninaHub.Tests.Stubs; + +namespace TarninaHub.Tests.Infrastructure; + +public class MiddlemanAssistantTests +{ + private static InnovationResponse SampleInnovation() => new( + "innovation:klub", + "Klub sąsiedzki", + "Miejsce spotkań i edukacji cyfrowej", + "Opis", + "social_challenge:seniorzy", + "pilot", + "5000 PLN", + null, + [], + true, + DateTimeOffset.UtcNow); + + private static MiddlemanAssistant CreateAssistant(InnovationResponse? innovation) => + new( + new StubInnovationLookup(innovation), + new NullLlmProvider(), + new InMemoryLlmRequestLimiter(30)); + + [Fact] + public async Task AdaptAsync_ReturnsNullWhenInnovationMissing() + { + var assistant = CreateAssistant(null); + + var result = await assistant.AdaptAsync( + new MiddlemanRequest("innovation:missing", null, null), + CancellationToken.None); + + Assert.Null(result); + } + + [Fact] + public async Task AdaptAsync_BuildsOfflineServiceForm() + { + var assistant = CreateAssistant(SampleInnovation()); + + var result = await assistant.AdaptAsync( + new MiddlemanRequest( + "innovation:klub", + new InstitutionProfile("gmina", "usługi opiekuńcze", ["seniorzy"]), + "opieka dzienna"), + CancellationToken.None); + + Assert.NotNull(result); + Assert.False(result!.FromLlm); + Assert.Contains("Klub sąsiedzki", result.ServiceForm); + Assert.Contains("seniorzy", result.ServiceForm); + Assert.NotEmpty(result.RequiredResources); + Assert.NotEmpty(result.Rationale); + } +} diff --git a/tests/TarninaHub.Tests/Stubs/StubApplicationService.cs b/tests/TarninaHub.Tests/Stubs/StubApplicationService.cs new file mode 100644 index 0000000..19a2ab1 --- /dev/null +++ b/tests/TarninaHub.Tests/Stubs/StubApplicationService.cs @@ -0,0 +1,27 @@ +using TarninaHub.Application.Abstractions; +using TarninaHub.Application.Dtos; + +namespace TarninaHub.Tests.Stubs; + +/// Configurable IApplicationService stub for controller tests. +public sealed class StubApplicationService : IApplicationService +{ + private readonly CreateApplicationResult _createResult; + + public StubApplicationService(CreateApplicationResult createResult) + { + _createResult = createResult; + } + + public Task CreateAsync( + string userId, + CreateApplicationRequest request, + CancellationToken cancellationToken) => Task.FromResult(_createResult); + + public Task GetAsync(string applicationId, CancellationToken cancellationToken) => + Task.FromResult(_createResult.Application); + + public Task> ListForAuthorAsync( + string authorId, + CancellationToken cancellationToken) => Task.FromResult>([]); +} diff --git a/tests/TarninaHub.Tests/Stubs/StubIdeaAssistant.cs b/tests/TarninaHub.Tests/Stubs/StubIdeaAssistant.cs new file mode 100644 index 0000000..df50115 --- /dev/null +++ b/tests/TarninaHub.Tests/Stubs/StubIdeaAssistant.cs @@ -0,0 +1,14 @@ +using TarninaHub.Application.Abstractions; +using TarninaHub.Application.Dtos; + +namespace TarninaHub.Tests.Stubs; + +/// No-op IIdeaAssistant stub for controller tests. +public sealed class StubIdeaAssistant : IIdeaAssistant +{ + public Task AssistAsync( + string ideaId, + IdeaAssistRequest request, + CancellationToken cancellationToken) => + Task.FromResult(new IdeaAssistResponse([], "opis", false)); +} diff --git a/tests/TarninaHub.Tests/Stubs/StubIdeaService.cs b/tests/TarninaHub.Tests/Stubs/StubIdeaService.cs index f20dc62..b3cd3ff 100644 --- a/tests/TarninaHub.Tests/Stubs/StubIdeaService.cs +++ b/tests/TarninaHub.Tests/Stubs/StubIdeaService.cs @@ -17,6 +17,9 @@ public sealed class StubIdeaService : IIdeaService public Task> ListForAuthorAsync(string authorId, CancellationToken cancellationToken) => Task.FromResult>(Idea is null ? [] : [Idea]); + public Task> ListDistinctAuthorIdsAsync(CancellationToken cancellationToken) => + Task.FromResult>(Idea is null ? [] : [Idea.AuthorId]); + public Task UpdateAsync(string ideaId, UpdateIdeaRequest request, CancellationToken cancellationToken) => Task.FromResult(Idea); diff --git a/tests/TarninaHub.Tests/Stubs/StubInnovationLookup.cs b/tests/TarninaHub.Tests/Stubs/StubInnovationLookup.cs new file mode 100644 index 0000000..af28ab6 --- /dev/null +++ b/tests/TarninaHub.Tests/Stubs/StubInnovationLookup.cs @@ -0,0 +1,34 @@ +using TarninaHub.Application.Abstractions; +using TarninaHub.Application.Dtos; + +namespace TarninaHub.Tests.Stubs; + +/// Innovation lookup stub returning a fixed (or null) innovation. +public sealed class StubInnovationLookup : IInnovationService +{ + private readonly InnovationResponse? _innovation; + + public StubInnovationLookup(InnovationResponse? innovation) + { + _innovation = innovation; + } + + public Task> ListAsync( + string? challengeId, + string? stage, + string? query, + bool onlyPublished, + CancellationToken cancellationToken) => Task.FromResult>([]); + + public Task GetAsync(string innovationId, CancellationToken cancellationToken) => + Task.FromResult(_innovation); + + public Task CreateAsync( + InnovationRequest request, + CancellationToken cancellationToken) => throw new NotSupportedException(); + + public Task UpdateAsync( + string innovationId, + InnovationRequest request, + CancellationToken cancellationToken) => throw new NotSupportedException(); +} diff --git a/tests/TarninaHub.Tests/Stubs/StubInnovationService.cs b/tests/TarninaHub.Tests/Stubs/StubInnovationService.cs new file mode 100644 index 0000000..d94ed10 --- /dev/null +++ b/tests/TarninaHub.Tests/Stubs/StubInnovationService.cs @@ -0,0 +1,38 @@ +using TarninaHub.Application.Abstractions; +using TarninaHub.Application.Dtos; + +namespace TarninaHub.Tests.Stubs; + +/// Configurable IInnovationService stub for controller tests. +public sealed class StubInnovationService : IInnovationService +{ + public Task> ListAsync( + string? challengeId, + string? stage, + string? query, + bool onlyPublished, + CancellationToken cancellationToken) => Task.FromResult>([]); + + public Task GetAsync(string innovationId, CancellationToken cancellationToken) => + Task.FromResult(null); + + public Task CreateAsync( + InnovationRequest request, + CancellationToken cancellationToken) => Task.FromResult(new InnovationResponse( + "innovation:new", + request.Title, + request.Summary ?? string.Empty, + request.Description ?? string.Empty, + request.ChallengeId, + request.Stage, + request.Cost, + request.Contact, + request.Media ?? [], + request.Published, + DateTimeOffset.UtcNow)); + + public Task UpdateAsync( + string innovationId, + InnovationRequest request, + CancellationToken cancellationToken) => Task.FromResult(null); +} diff --git a/tests/TarninaHub.Tests/Stubs/StubNotificationService.cs b/tests/TarninaHub.Tests/Stubs/StubNotificationService.cs new file mode 100644 index 0000000..298a4f1 --- /dev/null +++ b/tests/TarninaHub.Tests/Stubs/StubNotificationService.cs @@ -0,0 +1,40 @@ +using TarninaHub.Application.Abstractions; +using TarninaHub.Application.Dtos; + +namespace TarninaHub.Tests.Stubs; + +/// No-op INotificationService stub for controller tests. +public sealed class StubNotificationService : INotificationService +{ + public int StaffNotifications { get; private set; } + + public int UserNotifications { get; private set; } + + public Task NotifyStaffAsync( + string type, + IReadOnlyDictionary payload, + CancellationToken cancellationToken) + { + StaffNotifications++; + return Task.CompletedTask; + } + + public Task NotifyUserAsync( + string userId, + string type, + IReadOnlyDictionary payload, + CancellationToken cancellationToken) + { + UserNotifications++; + return Task.CompletedTask; + } + + public Task> ListForUserAsync( + string userId, + CancellationToken cancellationToken) => Task.FromResult>([]); + + public Task MarkReadAsync( + string notificationId, + string userId, + CancellationToken cancellationToken) => Task.FromResult(null); +} diff --git a/tests/TarninaHub.Tests/Stubs/StubSubmissionService.cs b/tests/TarninaHub.Tests/Stubs/StubSubmissionService.cs new file mode 100644 index 0000000..265c58a --- /dev/null +++ b/tests/TarninaHub.Tests/Stubs/StubSubmissionService.cs @@ -0,0 +1,41 @@ +using TarninaHub.Application.Abstractions; +using TarninaHub.Application.Dtos; + +namespace TarninaHub.Tests.Stubs; + +/// Configurable ISubmissionService stub for controller tests. +public sealed class StubSubmissionService : ISubmissionService +{ + public SubmissionResponse? Current { get; set; } + + public SubmissionResponse? UpdateResult { get; set; } + + public string? LastRequestedStatus { get; private set; } + + public Task CreateAsync( + string authorId, + CreateSubmissionRequest request, + CancellationToken cancellationToken) => Task.FromResult(Current ?? throw new InvalidOperationException()); + + public Task> ListAsync( + string viewerId, + bool isStaff, + string? status, + int page, + int pageSize, + CancellationToken cancellationToken) => Task.FromResult( + new PagedResponse([], page, pageSize, 0)); + + public Task GetAsync(string submissionId, CancellationToken cancellationToken) => + Task.FromResult(Current); + + public Task UpdateStatusAsync( + string submissionId, + string newStatus, + CancellationToken cancellationToken) + { + LastRequestedStatus = newStatus; + + return Task.FromResult(UpdateResult); + } +} diff --git a/tests/TarninaHub.Tests/Stubs/StubTestFeedbackService.cs b/tests/TarninaHub.Tests/Stubs/StubTestFeedbackService.cs new file mode 100644 index 0000000..456b1a4 --- /dev/null +++ b/tests/TarninaHub.Tests/Stubs/StubTestFeedbackService.cs @@ -0,0 +1,18 @@ +using TarninaHub.Application.Abstractions; +using TarninaHub.Application.Dtos; + +namespace TarninaHub.Tests.Stubs; + +/// No-op ITestFeedbackService stub for controller tests. +public sealed class StubTestFeedbackService : ITestFeedbackService +{ + public Task CreateAsync( + string innovationId, + string authorId, + CreateTestFeedbackRequest request, + CancellationToken cancellationToken) => Task.FromResult(null); + + public Task> ListForInnovationAsync( + string innovationId, + CancellationToken cancellationToken) => Task.FromResult>([]); +}