test(ideas): cover workflow, application window and controller outcomes
Add unit tests for idea status transitions, granted application windows and applications controller status mapping (409 when call closed, 201 on success).
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
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 ApplicationsControllerTests
|
||||
{
|
||||
private static ApplicationsController CreateController(
|
||||
IApplicationService applicationService,
|
||||
IIdeaService? ideaService = null)
|
||||
{
|
||||
var controller = new ApplicationsController(applicationService, ideaService ?? new StubIdeaService());
|
||||
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_ReturnsConflictWhenCallNotOpen()
|
||||
{
|
||||
var service = new StubApplicationService(
|
||||
new CreateApplicationResult(CreateApplicationOutcome.CallNotOpen, null));
|
||||
var controller = CreateController(service);
|
||||
|
||||
var result = await controller.Create(
|
||||
new CreateApplicationRequest("grant_call:x", "idea:y", null),
|
||||
CancellationToken.None);
|
||||
|
||||
var conflict = Assert.IsType<ConflictObjectResult>(result.Result);
|
||||
Assert.Equal(StatusCodes.Status409Conflict, conflict.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Create_ReturnsCreatedWhenSuccessful()
|
||||
{
|
||||
var application = new ApplicationResponse(
|
||||
"application:new",
|
||||
"grant_call:x",
|
||||
"idea:y",
|
||||
new Dictionary<string, object?>(),
|
||||
"draft",
|
||||
DateTimeOffset.UtcNow);
|
||||
var service = new StubApplicationService(
|
||||
new CreateApplicationResult(CreateApplicationOutcome.Created, application));
|
||||
var controller = CreateController(service);
|
||||
|
||||
var result = await controller.Create(
|
||||
new CreateApplicationRequest("grant_call:x", "idea:y", null),
|
||||
CancellationToken.None);
|
||||
|
||||
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,40 @@
|
||||
using TarninaHub.Domain.Enums;
|
||||
using TarninaHub.Domain.Rules;
|
||||
|
||||
namespace TarninaHub.Tests.Domain;
|
||||
|
||||
public class IdeaAndApplicationRulesTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(IdeaStatus.Draft, IdeaStatus.Submitted, true)]
|
||||
[InlineData(IdeaStatus.Submitted, IdeaStatus.InReview, true)]
|
||||
[InlineData(IdeaStatus.InReview, IdeaStatus.Accepted, true)]
|
||||
[InlineData(IdeaStatus.InReview, IdeaStatus.Rejected, true)]
|
||||
[InlineData(IdeaStatus.Draft, IdeaStatus.Accepted, false)]
|
||||
[InlineData(IdeaStatus.Accepted, IdeaStatus.Draft, false)]
|
||||
public void IdeaWorkflow_EnforcesTransitions(IdeaStatus from, IdeaStatus to, bool expected)
|
||||
{
|
||||
Assert.Equal(expected, IdeaWorkflow.CanTransition(from, to));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IdeaWorkflow_RecognizesTerminalStates()
|
||||
{
|
||||
Assert.True(IdeaWorkflow.IsTerminal(IdeaStatus.Accepted));
|
||||
Assert.True(IdeaWorkflow.IsTerminal(IdeaStatus.Rejected));
|
||||
Assert.False(IdeaWorkflow.IsTerminal(IdeaStatus.Draft));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ApplicationWindow_IsOpenOnlyWithinOpenCallWindow()
|
||||
{
|
||||
var opensAt = new DateTimeOffset(2026, 9, 1, 0, 0, 0, TimeSpan.Zero);
|
||||
var closesAt = new DateTimeOffset(2026, 12, 31, 23, 59, 59, TimeSpan.Zero);
|
||||
var now = new DateTimeOffset(2026, 10, 4, 12, 0, 0, TimeSpan.Zero);
|
||||
|
||||
Assert.True(ApplicationWindow.IsOpen("open", opensAt, closesAt, now));
|
||||
Assert.False(ApplicationWindow.IsOpen("closed", opensAt, closesAt, now));
|
||||
Assert.False(ApplicationWindow.IsOpen("open", opensAt, closesAt, opensAt.AddDays(-1)));
|
||||
Assert.False(ApplicationWindow.IsOpen("open", opensAt, closesAt, closesAt.AddSeconds(1)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using TarninaHub.Application.Abstractions;
|
||||
using TarninaHub.Application.Dtos;
|
||||
|
||||
namespace TarninaHub.Tests.Stubs;
|
||||
|
||||
/// <summary>Minimal IIdeaService stub for controller tests.</summary>
|
||||
public sealed class StubIdeaService : IIdeaService
|
||||
{
|
||||
public IdeaResponse? Idea { get; set; }
|
||||
|
||||
public Task<IdeaResponse> CreateAsync(string authorId, CreateIdeaRequest request, CancellationToken cancellationToken) =>
|
||||
Task.FromResult(Idea ?? throw new InvalidOperationException());
|
||||
|
||||
public Task<IdeaResponse?> GetAsync(string ideaId, CancellationToken cancellationToken) =>
|
||||
Task.FromResult(Idea);
|
||||
|
||||
public Task<IReadOnlyList<IdeaResponse>> ListForAuthorAsync(string authorId, CancellationToken cancellationToken) =>
|
||||
Task.FromResult<IReadOnlyList<IdeaResponse>>(Idea is null ? [] : [Idea]);
|
||||
|
||||
public Task<IdeaResponse?> UpdateAsync(string ideaId, UpdateIdeaRequest request, CancellationToken cancellationToken) =>
|
||||
Task.FromResult(Idea);
|
||||
|
||||
public Task<IdeaCardResponse?> GetCardAsync(string ideaId, CancellationToken cancellationToken) =>
|
||||
Task.FromResult<IdeaCardResponse?>(null);
|
||||
|
||||
public Task<IdeaCardResponse?> UpdateCardAsync(string ideaId, IdeaCardRequest request, CancellationToken cancellationToken) =>
|
||||
Task.FromResult<IdeaCardResponse?>(null);
|
||||
}
|
||||
Reference in New Issue
Block a user