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.
This commit is contained in:
@@ -0,0 +1,60 @@
|
|||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace TarninaHub.Tests.Architecture;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
public class NoObjectValuedDictionariesTests
|
||||||
|
{
|
||||||
|
private static readonly Regex Forbidden = new(@"Dictionary<string,\s*object", RegexOptions.Compiled);
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DataContracts_MustNotUseObjectValuedDictionaries()
|
||||||
|
{
|
||||||
|
var root = FindRepositoryRoot();
|
||||||
|
var files = new List<string>();
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -49,7 +49,7 @@ public class ApplicationsControllerTests
|
|||||||
"application:new",
|
"application:new",
|
||||||
"grant_call:x",
|
"grant_call:x",
|
||||||
"idea:y",
|
"idea:y",
|
||||||
new Dictionary<string, object?>(),
|
new Dictionary<string, string?>(),
|
||||||
"draft",
|
"draft",
|
||||||
DateTimeOffset.UtcNow);
|
DateTimeOffset.UtcNow);
|
||||||
var service = new StubApplicationService(
|
var service = new StubApplicationService(
|
||||||
@@ -62,26 +62,4 @@ public class ApplicationsControllerTests
|
|||||||
|
|
||||||
Assert.IsType<CreatedAtActionResult>(result.Result);
|
Assert.IsType<CreatedAtActionResult>(result.Result);
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class StubApplicationService : IApplicationService
|
|
||||||
{
|
|
||||||
private readonly CreateApplicationResult _createResult;
|
|
||||||
|
|
||||||
public StubApplicationService(CreateApplicationResult createResult)
|
|
||||||
{
|
|
||||||
_createResult = createResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<CreateApplicationResult> CreateAsync(
|
|
||||||
string userId,
|
|
||||||
CreateApplicationRequest request,
|
|
||||||
CancellationToken cancellationToken) => Task.FromResult(_createResult);
|
|
||||||
|
|
||||||
public Task<ApplicationResponse?> GetAsync(string applicationId, CancellationToken cancellationToken) =>
|
|
||||||
Task.FromResult(_createResult.Application);
|
|
||||||
|
|
||||||
public Task<IReadOnlyList<ApplicationResponse>> ListForAuthorAsync(
|
|
||||||
string authorId,
|
|
||||||
CancellationToken cancellationToken) => Task.FromResult<IReadOnlyList<ApplicationResponse>>([]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<CreatedAtActionResult>(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);
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ using TarninaHub.Application.Abstractions;
|
|||||||
using TarninaHub.Application.Dtos;
|
using TarninaHub.Application.Dtos;
|
||||||
using TarninaHub.Controllers;
|
using TarninaHub.Controllers;
|
||||||
using TarninaHub.Tests.Matchmaking;
|
using TarninaHub.Tests.Matchmaking;
|
||||||
|
using TarninaHub.Tests.Stubs;
|
||||||
|
|
||||||
namespace TarninaHub.Tests.Controllers;
|
namespace TarninaHub.Tests.Controllers;
|
||||||
|
|
||||||
@@ -12,7 +13,10 @@ public class InnovationsControllerTests
|
|||||||
{
|
{
|
||||||
private static InnovationsController CreateController(IInnovationService service)
|
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(
|
var identity = new ClaimsIdentity(
|
||||||
[new Claim("sub", "user:tester"), new Claim("role", "admin")],
|
[new Claim("sub", "user:tester"), new Claim("role", "admin")],
|
||||||
authenticationType: "test");
|
authenticationType: "test");
|
||||||
@@ -55,37 +59,4 @@ public class InnovationsControllerTests
|
|||||||
|
|
||||||
Assert.IsType<CreatedAtActionResult>(result.Result);
|
Assert.IsType<CreatedAtActionResult>(result.Result);
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class StubInnovationService : IInnovationService
|
|
||||||
{
|
|
||||||
public Task<IReadOnlyList<InnovationResponse>> ListAsync(
|
|
||||||
string? challengeId,
|
|
||||||
string? stage,
|
|
||||||
string? query,
|
|
||||||
bool onlyPublished,
|
|
||||||
CancellationToken cancellationToken) => Task.FromResult<IReadOnlyList<InnovationResponse>>([]);
|
|
||||||
|
|
||||||
public Task<InnovationResponse?> GetAsync(string innovationId, CancellationToken cancellationToken) =>
|
|
||||||
Task.FromResult<InnovationResponse?>(null);
|
|
||||||
|
|
||||||
public Task<InnovationResponse> 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<InnovationResponse?> UpdateAsync(
|
|
||||||
string innovationId,
|
|
||||||
InnovationRequest request,
|
|
||||||
CancellationToken cancellationToken) => Task.FromResult<InnovationResponse?>(null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using TarninaHub.Application.Abstractions;
|
|||||||
using TarninaHub.Application.Dtos;
|
using TarninaHub.Application.Dtos;
|
||||||
using TarninaHub.Controllers;
|
using TarninaHub.Controllers;
|
||||||
using TarninaHub.Tests.Matchmaking;
|
using TarninaHub.Tests.Matchmaking;
|
||||||
|
using TarninaHub.Tests.Stubs;
|
||||||
|
|
||||||
namespace TarninaHub.Tests.Controllers;
|
namespace TarninaHub.Tests.Controllers;
|
||||||
|
|
||||||
@@ -12,7 +13,10 @@ public class SubmissionsControllerTests
|
|||||||
{
|
{
|
||||||
private static SubmissionsController CreateController(ISubmissionService service, string role = "operator")
|
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(
|
var identity = new ClaimsIdentity(
|
||||||
[new Claim("sub", "user:tester"), new Claim("role", role)],
|
[new Claim("sub", "user:tester"), new Claim("role", role)],
|
||||||
authenticationType: "test");
|
authenticationType: "test");
|
||||||
@@ -81,40 +85,4 @@ public class SubmissionsControllerTests
|
|||||||
|
|
||||||
Assert.IsType<NotFoundResult>(result.Result);
|
Assert.IsType<NotFoundResult>(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<SubmissionResponse> CreateAsync(
|
|
||||||
string authorId,
|
|
||||||
CreateSubmissionRequest request,
|
|
||||||
CancellationToken cancellationToken) => Task.FromResult(SampleSubmission("new"));
|
|
||||||
|
|
||||||
public Task<PagedResponse<SubmissionResponse>> ListAsync(
|
|
||||||
string viewerId,
|
|
||||||
bool isStaff,
|
|
||||||
string? status,
|
|
||||||
int page,
|
|
||||||
int pageSize,
|
|
||||||
CancellationToken cancellationToken) => Task.FromResult(
|
|
||||||
new PagedResponse<SubmissionResponse>([], page, pageSize, 0));
|
|
||||||
|
|
||||||
public Task<SubmissionResponse?> GetAsync(string submissionId, CancellationToken cancellationToken) =>
|
|
||||||
Task.FromResult(Current);
|
|
||||||
|
|
||||||
public Task<SubmissionResponse?> UpdateStatusAsync(
|
|
||||||
string submissionId,
|
|
||||||
string newStatus,
|
|
||||||
CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
LastRequestedStatus = newStatus;
|
|
||||||
|
|
||||||
return Task.FromResult(UpdateResult);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using TarninaHub.Application.Abstractions;
|
||||||
|
using TarninaHub.Application.Dtos;
|
||||||
|
|
||||||
|
namespace TarninaHub.Tests.Stubs;
|
||||||
|
|
||||||
|
/// <summary>Configurable IApplicationService stub for controller tests.</summary>
|
||||||
|
public sealed class StubApplicationService : IApplicationService
|
||||||
|
{
|
||||||
|
private readonly CreateApplicationResult _createResult;
|
||||||
|
|
||||||
|
public StubApplicationService(CreateApplicationResult createResult)
|
||||||
|
{
|
||||||
|
_createResult = createResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<CreateApplicationResult> CreateAsync(
|
||||||
|
string userId,
|
||||||
|
CreateApplicationRequest request,
|
||||||
|
CancellationToken cancellationToken) => Task.FromResult(_createResult);
|
||||||
|
|
||||||
|
public Task<ApplicationResponse?> GetAsync(string applicationId, CancellationToken cancellationToken) =>
|
||||||
|
Task.FromResult(_createResult.Application);
|
||||||
|
|
||||||
|
public Task<IReadOnlyList<ApplicationResponse>> ListForAuthorAsync(
|
||||||
|
string authorId,
|
||||||
|
CancellationToken cancellationToken) => Task.FromResult<IReadOnlyList<ApplicationResponse>>([]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using TarninaHub.Application.Abstractions;
|
||||||
|
using TarninaHub.Application.Dtos;
|
||||||
|
|
||||||
|
namespace TarninaHub.Tests.Stubs;
|
||||||
|
|
||||||
|
/// <summary>No-op IIdeaAssistant stub for controller tests.</summary>
|
||||||
|
public sealed class StubIdeaAssistant : IIdeaAssistant
|
||||||
|
{
|
||||||
|
public Task<IdeaAssistResponse?> AssistAsync(
|
||||||
|
string ideaId,
|
||||||
|
IdeaAssistRequest request,
|
||||||
|
CancellationToken cancellationToken) =>
|
||||||
|
Task.FromResult<IdeaAssistResponse?>(new IdeaAssistResponse([], "opis", false));
|
||||||
|
}
|
||||||
@@ -17,6 +17,9 @@ public sealed class StubIdeaService : IIdeaService
|
|||||||
public Task<IReadOnlyList<IdeaResponse>> ListForAuthorAsync(string authorId, CancellationToken cancellationToken) =>
|
public Task<IReadOnlyList<IdeaResponse>> ListForAuthorAsync(string authorId, CancellationToken cancellationToken) =>
|
||||||
Task.FromResult<IReadOnlyList<IdeaResponse>>(Idea is null ? [] : [Idea]);
|
Task.FromResult<IReadOnlyList<IdeaResponse>>(Idea is null ? [] : [Idea]);
|
||||||
|
|
||||||
|
public Task<IReadOnlyList<string>> ListDistinctAuthorIdsAsync(CancellationToken cancellationToken) =>
|
||||||
|
Task.FromResult<IReadOnlyList<string>>(Idea is null ? [] : [Idea.AuthorId]);
|
||||||
|
|
||||||
public Task<IdeaResponse?> UpdateAsync(string ideaId, UpdateIdeaRequest request, CancellationToken cancellationToken) =>
|
public Task<IdeaResponse?> UpdateAsync(string ideaId, UpdateIdeaRequest request, CancellationToken cancellationToken) =>
|
||||||
Task.FromResult(Idea);
|
Task.FromResult(Idea);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using TarninaHub.Application.Abstractions;
|
||||||
|
using TarninaHub.Application.Dtos;
|
||||||
|
|
||||||
|
namespace TarninaHub.Tests.Stubs;
|
||||||
|
|
||||||
|
/// <summary>Innovation lookup stub returning a fixed (or null) innovation.</summary>
|
||||||
|
public sealed class StubInnovationLookup : IInnovationService
|
||||||
|
{
|
||||||
|
private readonly InnovationResponse? _innovation;
|
||||||
|
|
||||||
|
public StubInnovationLookup(InnovationResponse? innovation)
|
||||||
|
{
|
||||||
|
_innovation = innovation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<IReadOnlyList<InnovationResponse>> ListAsync(
|
||||||
|
string? challengeId,
|
||||||
|
string? stage,
|
||||||
|
string? query,
|
||||||
|
bool onlyPublished,
|
||||||
|
CancellationToken cancellationToken) => Task.FromResult<IReadOnlyList<InnovationResponse>>([]);
|
||||||
|
|
||||||
|
public Task<InnovationResponse?> GetAsync(string innovationId, CancellationToken cancellationToken) =>
|
||||||
|
Task.FromResult(_innovation);
|
||||||
|
|
||||||
|
public Task<InnovationResponse> CreateAsync(
|
||||||
|
InnovationRequest request,
|
||||||
|
CancellationToken cancellationToken) => throw new NotSupportedException();
|
||||||
|
|
||||||
|
public Task<InnovationResponse?> UpdateAsync(
|
||||||
|
string innovationId,
|
||||||
|
InnovationRequest request,
|
||||||
|
CancellationToken cancellationToken) => throw new NotSupportedException();
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
using TarninaHub.Application.Abstractions;
|
||||||
|
using TarninaHub.Application.Dtos;
|
||||||
|
|
||||||
|
namespace TarninaHub.Tests.Stubs;
|
||||||
|
|
||||||
|
/// <summary>Configurable IInnovationService stub for controller tests.</summary>
|
||||||
|
public sealed class StubInnovationService : IInnovationService
|
||||||
|
{
|
||||||
|
public Task<IReadOnlyList<InnovationResponse>> ListAsync(
|
||||||
|
string? challengeId,
|
||||||
|
string? stage,
|
||||||
|
string? query,
|
||||||
|
bool onlyPublished,
|
||||||
|
CancellationToken cancellationToken) => Task.FromResult<IReadOnlyList<InnovationResponse>>([]);
|
||||||
|
|
||||||
|
public Task<InnovationResponse?> GetAsync(string innovationId, CancellationToken cancellationToken) =>
|
||||||
|
Task.FromResult<InnovationResponse?>(null);
|
||||||
|
|
||||||
|
public Task<InnovationResponse> 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<InnovationResponse?> UpdateAsync(
|
||||||
|
string innovationId,
|
||||||
|
InnovationRequest request,
|
||||||
|
CancellationToken cancellationToken) => Task.FromResult<InnovationResponse?>(null);
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
using TarninaHub.Application.Abstractions;
|
||||||
|
using TarninaHub.Application.Dtos;
|
||||||
|
|
||||||
|
namespace TarninaHub.Tests.Stubs;
|
||||||
|
|
||||||
|
/// <summary>No-op INotificationService stub for controller tests.</summary>
|
||||||
|
public sealed class StubNotificationService : INotificationService
|
||||||
|
{
|
||||||
|
public int StaffNotifications { get; private set; }
|
||||||
|
|
||||||
|
public int UserNotifications { get; private set; }
|
||||||
|
|
||||||
|
public Task NotifyStaffAsync(
|
||||||
|
string type,
|
||||||
|
IReadOnlyDictionary<string, string?> payload,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
StaffNotifications++;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task NotifyUserAsync(
|
||||||
|
string userId,
|
||||||
|
string type,
|
||||||
|
IReadOnlyDictionary<string, string?> payload,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
UserNotifications++;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<IReadOnlyList<NotificationResponse>> ListForUserAsync(
|
||||||
|
string userId,
|
||||||
|
CancellationToken cancellationToken) => Task.FromResult<IReadOnlyList<NotificationResponse>>([]);
|
||||||
|
|
||||||
|
public Task<NotificationResponse?> MarkReadAsync(
|
||||||
|
string notificationId,
|
||||||
|
string userId,
|
||||||
|
CancellationToken cancellationToken) => Task.FromResult<NotificationResponse?>(null);
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
using TarninaHub.Application.Abstractions;
|
||||||
|
using TarninaHub.Application.Dtos;
|
||||||
|
|
||||||
|
namespace TarninaHub.Tests.Stubs;
|
||||||
|
|
||||||
|
/// <summary>Configurable ISubmissionService stub for controller tests.</summary>
|
||||||
|
public sealed class StubSubmissionService : ISubmissionService
|
||||||
|
{
|
||||||
|
public SubmissionResponse? Current { get; set; }
|
||||||
|
|
||||||
|
public SubmissionResponse? UpdateResult { get; set; }
|
||||||
|
|
||||||
|
public string? LastRequestedStatus { get; private set; }
|
||||||
|
|
||||||
|
public Task<SubmissionResponse> CreateAsync(
|
||||||
|
string authorId,
|
||||||
|
CreateSubmissionRequest request,
|
||||||
|
CancellationToken cancellationToken) => Task.FromResult(Current ?? throw new InvalidOperationException());
|
||||||
|
|
||||||
|
public Task<PagedResponse<SubmissionResponse>> ListAsync(
|
||||||
|
string viewerId,
|
||||||
|
bool isStaff,
|
||||||
|
string? status,
|
||||||
|
int page,
|
||||||
|
int pageSize,
|
||||||
|
CancellationToken cancellationToken) => Task.FromResult(
|
||||||
|
new PagedResponse<SubmissionResponse>([], page, pageSize, 0));
|
||||||
|
|
||||||
|
public Task<SubmissionResponse?> GetAsync(string submissionId, CancellationToken cancellationToken) =>
|
||||||
|
Task.FromResult(Current);
|
||||||
|
|
||||||
|
public Task<SubmissionResponse?> UpdateStatusAsync(
|
||||||
|
string submissionId,
|
||||||
|
string newStatus,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
LastRequestedStatus = newStatus;
|
||||||
|
|
||||||
|
return Task.FromResult(UpdateResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using TarninaHub.Application.Abstractions;
|
||||||
|
using TarninaHub.Application.Dtos;
|
||||||
|
|
||||||
|
namespace TarninaHub.Tests.Stubs;
|
||||||
|
|
||||||
|
/// <summary>No-op ITestFeedbackService stub for controller tests.</summary>
|
||||||
|
public sealed class StubTestFeedbackService : ITestFeedbackService
|
||||||
|
{
|
||||||
|
public Task<TestFeedbackResponse?> CreateAsync(
|
||||||
|
string innovationId,
|
||||||
|
string authorId,
|
||||||
|
CreateTestFeedbackRequest request,
|
||||||
|
CancellationToken cancellationToken) => Task.FromResult<TestFeedbackResponse?>(null);
|
||||||
|
|
||||||
|
public Task<IReadOnlyList<TestFeedbackResponse>> ListForInnovationAsync(
|
||||||
|
string innovationId,
|
||||||
|
CancellationToken cancellationToken) => Task.FromResult<IReadOnlyList<TestFeedbackResponse>>([]);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user