Skip to content

Commit de27e12

Browse files
committed
VPR-54 test(scheduler): JobsController action-result coverage
Adds 17 unit tests over JobsController, asserting each action returns the expected ActionResult / status code on success and on every documented error path (system-job protection, marker not found, concurrency conflict, invalid base64 rowversion, missing body, missing HTTP user). Mocks ISchedulerJobsService and IUserHelper so the tests exercise only the controller's translation layer, not the service.
1 parent 5cc08bd commit de27e12

1 file changed

Lines changed: 255 additions & 0 deletions

File tree

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
using Microsoft.AspNetCore.Http;
2+
using Microsoft.AspNetCore.Mvc;
3+
using Microsoft.Extensions.Logging;
4+
using NSubstitute;
5+
using NSubstitute.ExceptionExtensions;
6+
using Viper.Areas.Scheduler.Controllers;
7+
using Viper.Areas.Scheduler.Models.DTOs.Responses;
8+
using Viper.Areas.Scheduler.Services;
9+
using Viper.Models.AAUD;
10+
11+
namespace Viper.test.Scheduler
12+
{
13+
public sealed class JobsControllerTests
14+
{
15+
private readonly ISchedulerJobsService _service;
16+
private readonly IUserHelper _userHelper;
17+
private readonly ILogger<JobsController> _logger;
18+
private readonly JobsController _sut;
19+
20+
public JobsControllerTests()
21+
{
22+
_service = Substitute.For<ISchedulerJobsService>();
23+
_userHelper = Substitute.For<IUserHelper>();
24+
_logger = Substitute.For<ILogger<JobsController>>();
25+
_sut = new JobsController(_service, _userHelper, _logger);
26+
}
27+
28+
// ──────────── ListJobs ────────────
29+
30+
[Fact]
31+
public async Task ListJobs_ReturnsOkWithServiceResult()
32+
{
33+
var dtos = new List<SchedulerJobDto> { new() { Id = "raps:role-refresh" } };
34+
_service.ListJobsAsync(Arg.Any<CancellationToken>()).Returns(dtos);
35+
36+
var result = await _sut.ListJobs(CancellationToken.None);
37+
38+
var ok = Assert.IsType<OkObjectResult>(result.Result);
39+
Assert.Same(dtos, ok.Value);
40+
}
41+
42+
// ──────────── GetJob ────────────
43+
44+
[Fact]
45+
public async Task GetJob_ReturnsOkWhenServiceFindsJob()
46+
{
47+
var dto = new SchedulerJobDto { Id = "raps:role-refresh" };
48+
_service.GetJobAsync("raps:role-refresh", Arg.Any<CancellationToken>()).Returns(dto);
49+
50+
var result = await _sut.GetJob("raps:role-refresh", CancellationToken.None);
51+
52+
var ok = Assert.IsType<OkObjectResult>(result.Result);
53+
Assert.Same(dto, ok.Value);
54+
}
55+
56+
[Fact]
57+
public async Task GetJob_ReturnsNotFoundWhenServiceReturnsNull()
58+
{
59+
_service.GetJobAsync(Arg.Any<string>(), Arg.Any<CancellationToken>()).Returns((SchedulerJobDto?)null);
60+
61+
var result = await _sut.GetJob("missing", CancellationToken.None);
62+
63+
Assert.IsType<NotFoundResult>(result.Result);
64+
}
65+
66+
// ──────────── PauseJob ────────────
67+
68+
[Fact]
69+
public async Task PauseJob_ReturnsOkWhenServiceCompletes()
70+
{
71+
_userHelper.GetCurrentUser().Returns(new AaudUser { LoginId = "alice" });
72+
var dto = new PauseResumeResultDto { Id = "raps:role-refresh", IsPaused = true };
73+
_service.PauseJobAsync("raps:role-refresh", "alice", Arg.Any<byte[]?>(), Arg.Any<CancellationToken>())
74+
.Returns(dto);
75+
76+
var result = await _sut.PauseJob("raps:role-refresh", new JobsController.PauseRequest(), CancellationToken.None);
77+
78+
var ok = Assert.IsType<OkObjectResult>(result.Result);
79+
Assert.Same(dto, ok.Value);
80+
}
81+
82+
[Fact]
83+
public async Task PauseJob_Returns202WhenDeregistrationPending()
84+
{
85+
_userHelper.GetCurrentUser().Returns(new AaudUser { LoginId = "alice" });
86+
var dto = new PauseResumeResultDto { Id = "raps:role-refresh", IsPaused = true, DeregistrationPending = true };
87+
_service.PauseJobAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<byte[]?>(), Arg.Any<CancellationToken>())
88+
.Returns(dto);
89+
90+
var result = await _sut.PauseJob("raps:role-refresh", null, CancellationToken.None);
91+
92+
var status = Assert.IsType<ObjectResult>(result.Result);
93+
Assert.Equal(StatusCodes.Status202Accepted, status.StatusCode);
94+
Assert.Same(dto, status.Value);
95+
}
96+
97+
[Fact]
98+
public async Task PauseJob_Returns403OnSystemJobProtection()
99+
{
100+
_userHelper.GetCurrentUser().Returns(new AaudUser { LoginId = "alice" });
101+
_service.PauseJobAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<byte[]?>(), Arg.Any<CancellationToken>())
102+
.ThrowsAsyncForAnyArgs(new SchedulerSystemJobProtectedException("__scheduler:reconcile"));
103+
104+
var result = await _sut.PauseJob("__scheduler:reconcile", null, CancellationToken.None);
105+
106+
var status = Assert.IsType<ObjectResult>(result.Result);
107+
Assert.Equal(StatusCodes.Status403Forbidden, status.StatusCode);
108+
}
109+
110+
[Fact]
111+
public async Task PauseJob_Returns404WhenJobMissing()
112+
{
113+
_userHelper.GetCurrentUser().Returns(new AaudUser { LoginId = "alice" });
114+
_service.PauseJobAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<byte[]?>(), Arg.Any<CancellationToken>())
115+
.ThrowsAsyncForAnyArgs(new SchedulerJobNotFoundException("nope"));
116+
117+
var result = await _sut.PauseJob("nope", null, CancellationToken.None);
118+
119+
Assert.IsType<NotFoundResult>(result.Result);
120+
}
121+
122+
[Fact]
123+
public async Task PauseJob_Returns409OnConcurrencyConflict()
124+
{
125+
_userHelper.GetCurrentUser().Returns(new AaudUser { LoginId = "alice" });
126+
_service.PauseJobAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<byte[]?>(), Arg.Any<CancellationToken>())
127+
.ThrowsAsyncForAnyArgs(new SchedulerConcurrencyException("raps:role-refresh"));
128+
129+
var result = await _sut.PauseJob("raps:role-refresh", null, CancellationToken.None);
130+
131+
Assert.IsType<ConflictObjectResult>(result.Result);
132+
}
133+
134+
[Fact]
135+
public async Task PauseJob_Returns400OnInvalidBase64RowVersion()
136+
{
137+
_userHelper.GetCurrentUser().Returns(new AaudUser { LoginId = "alice" });
138+
139+
var result = await _sut.PauseJob(
140+
"raps:role-refresh",
141+
new JobsController.PauseRequest { RowVersion = "not-base-64!!!" },
142+
CancellationToken.None);
143+
144+
Assert.IsType<BadRequestObjectResult>(result.Result);
145+
await _service.DidNotReceiveWithAnyArgs().PauseJobAsync(default!, default!, default, default);
146+
}
147+
148+
[Fact]
149+
public async Task PauseJob_FallsBackToSchedulerActorWhenNoUser()
150+
{
151+
_userHelper.GetCurrentUser().Returns((AaudUser?)null);
152+
_service.PauseJobAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<byte[]?>(), Arg.Any<CancellationToken>())
153+
.Returns(new PauseResumeResultDto { Id = "raps:role-refresh", IsPaused = true });
154+
155+
await _sut.PauseJob("raps:role-refresh", null, CancellationToken.None);
156+
157+
await _service.Received(1).PauseJobAsync(
158+
"raps:role-refresh",
159+
ISchedulerJobsService.SchedulerActor,
160+
Arg.Any<byte[]?>(),
161+
Arg.Any<CancellationToken>());
162+
}
163+
164+
// ──────────── ResumeJob ────────────
165+
166+
[Fact]
167+
public async Task ResumeJob_ReturnsOkWhenServiceCompletes()
168+
{
169+
var dto = new PauseResumeResultDto { Id = "raps:role-refresh", IsPaused = false };
170+
_service.ResumeJobAsync(Arg.Any<string>(), Arg.Any<byte[]>(), Arg.Any<CancellationToken>()).Returns(dto);
171+
172+
var result = await _sut.ResumeJob(
173+
"raps:role-refresh",
174+
new JobsController.ResumeRequest { RowVersion = Convert.ToBase64String([1, 2, 3, 4]) },
175+
CancellationToken.None);
176+
177+
var ok = Assert.IsType<OkObjectResult>(result.Result);
178+
Assert.Same(dto, ok.Value);
179+
}
180+
181+
[Fact]
182+
public async Task ResumeJob_Returns400WhenRowVersionMissing()
183+
{
184+
var result = await _sut.ResumeJob("raps:role-refresh", new JobsController.ResumeRequest(), CancellationToken.None);
185+
186+
Assert.IsType<BadRequestObjectResult>(result.Result);
187+
await _service.DidNotReceiveWithAnyArgs().ResumeJobAsync(default!, default!, default);
188+
}
189+
190+
[Fact]
191+
public async Task ResumeJob_Returns400WhenBodyMissing()
192+
{
193+
var result = await _sut.ResumeJob("raps:role-refresh", null, CancellationToken.None);
194+
195+
Assert.IsType<BadRequestObjectResult>(result.Result);
196+
}
197+
198+
[Fact]
199+
public async Task ResumeJob_Returns400OnInvalidBase64()
200+
{
201+
var result = await _sut.ResumeJob(
202+
"raps:role-refresh",
203+
new JobsController.ResumeRequest { RowVersion = "***bad***" },
204+
CancellationToken.None);
205+
206+
Assert.IsType<BadRequestObjectResult>(result.Result);
207+
}
208+
209+
[Fact]
210+
public async Task ResumeJob_Returns403OnSystemJobProtection()
211+
{
212+
_service.ResumeJobAsync(Arg.Any<string>(), Arg.Any<byte[]>(), Arg.Any<CancellationToken>())
213+
.ThrowsAsyncForAnyArgs(new SchedulerSystemJobProtectedException("__scheduler:reconcile"));
214+
215+
var result = await _sut.ResumeJob(
216+
"__scheduler:reconcile",
217+
new JobsController.ResumeRequest { RowVersion = Convert.ToBase64String([1]) },
218+
CancellationToken.None);
219+
220+
var status = Assert.IsType<ObjectResult>(result.Result);
221+
Assert.Equal(StatusCodes.Status403Forbidden, status.StatusCode);
222+
}
223+
224+
[Fact]
225+
public async Task ResumeJob_Returns404WithMarkerNotFoundCodeWhenMissing()
226+
{
227+
_service.ResumeJobAsync(Arg.Any<string>(), Arg.Any<byte[]>(), Arg.Any<CancellationToken>())
228+
.ThrowsAsyncForAnyArgs(new SchedulerJobNotFoundException("nope"));
229+
230+
var result = await _sut.ResumeJob(
231+
"nope",
232+
new JobsController.ResumeRequest { RowVersion = Convert.ToBase64String([1]) },
233+
CancellationToken.None);
234+
235+
var notFound = Assert.IsType<NotFoundObjectResult>(result.Result);
236+
Assert.Equal(
237+
"marker_not_found",
238+
notFound.Value?.GetType().GetProperty("error")?.GetValue(notFound.Value));
239+
}
240+
241+
[Fact]
242+
public async Task ResumeJob_Returns409OnConcurrencyConflict()
243+
{
244+
_service.ResumeJobAsync(Arg.Any<string>(), Arg.Any<byte[]>(), Arg.Any<CancellationToken>())
245+
.ThrowsAsyncForAnyArgs(new SchedulerConcurrencyException("raps:role-refresh"));
246+
247+
var result = await _sut.ResumeJob(
248+
"raps:role-refresh",
249+
new JobsController.ResumeRequest { RowVersion = Convert.ToBase64String([1]) },
250+
CancellationToken.None);
251+
252+
Assert.IsType<ConflictObjectResult>(result.Result);
253+
}
254+
}
255+
}

0 commit comments

Comments
 (0)