|
|
|
|
@@ -306,4 +306,108 @@ public class FlowableWorkflowClientTests
|
|
|
|
|
await Assert.ThrowsAsync<HttpRequestException>(
|
|
|
|
|
() => client.CompleteBeoordelingAsync("task-1", BeoordelingsBesluit.Goedkeuren));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── S-14 (#15): 14-day beoordeling escalation → reassign to teamlead (ADR-0015) ───────────────
|
|
|
|
|
// The BPMN parks a parallel escalation token on a non-interrupting P14D boundary timer, surfaced as
|
|
|
|
|
// an external-worker job on the BeoordelingEscaleren topic. The worker reassigns the still-open
|
|
|
|
|
// Beoordelen task from the behandelaar group to teamlead, then completes the escalation job.
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task Acquire_escalation_jobs_posts_the_escalation_topic_and_parses_the_process_instance()
|
|
|
|
|
{
|
|
|
|
|
var capture = new RequestCapture();
|
|
|
|
|
var client = Client(capture.Responds(HttpStatusCode.OK,
|
|
|
|
|
"""[{"id":"job-9","processInstanceId":"pi-1"}]"""));
|
|
|
|
|
|
|
|
|
|
var jobs = await client.AcquireBeoordelingEscalatieJobsAsync(3);
|
|
|
|
|
|
|
|
|
|
var job = Assert.Single(jobs);
|
|
|
|
|
Assert.Equal("job-9", job.JobId);
|
|
|
|
|
Assert.Equal("pi-1", job.ProcessInstanceId);
|
|
|
|
|
Assert.Equal("http://flowable/flowable-rest/external-job-api/acquire/jobs",
|
|
|
|
|
capture.Seen!.RequestUri!.ToString());
|
|
|
|
|
Assert.Contains("\"topic\":\"BeoordelingEscaleren\"", capture.Body);
|
|
|
|
|
Assert.Contains("\"numberOfTasks\":3", capture.Body);
|
|
|
|
|
Assert.Contains("\"workerId\":\"worker-x\"", capture.Body);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Theory]
|
|
|
|
|
[InlineData("[]")]
|
|
|
|
|
[InlineData("null")]
|
|
|
|
|
public async Task Acquire_escalation_jobs_returns_empty_when_none_are_parked(string body)
|
|
|
|
|
{
|
|
|
|
|
var capture = new RequestCapture();
|
|
|
|
|
var client = Client(capture.Responds(HttpStatusCode.OK, body));
|
|
|
|
|
|
|
|
|
|
Assert.Empty(await client.AcquireBeoordelingEscalatieJobsAsync(1));
|
|
|
|
|
Assert.NotNull(capture.Seen);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task Reassign_moves_the_open_beoordelen_task_from_behandelaar_to_teamlead()
|
|
|
|
|
{
|
|
|
|
|
var requests = new List<(HttpMethod Method, string Url, string? Body)>();
|
|
|
|
|
var client = Client(new StubHandler(async req =>
|
|
|
|
|
{
|
|
|
|
|
requests.Add((req.Method, req.RequestUri!.ToString(),
|
|
|
|
|
req.Content is null ? null : await req.Content.ReadAsStringAsync()));
|
|
|
|
|
// The task query returns the still-open Beoordelen task parked in this instance.
|
|
|
|
|
return req.RequestUri!.AbsoluteUri.EndsWith("service/query/tasks")
|
|
|
|
|
? new HttpResponseMessage(HttpStatusCode.OK)
|
|
|
|
|
{
|
|
|
|
|
Content = new StringContent("""{"data":[{"id":"task-7"}],"total":1}""",
|
|
|
|
|
Encoding.UTF8, "application/json"),
|
|
|
|
|
}
|
|
|
|
|
: new HttpResponseMessage(HttpStatusCode.OK);
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
await client.ReassignBeoordelingToTeamleadAsync("pi-1");
|
|
|
|
|
|
|
|
|
|
// 1. Find the still-open Beoordelen task in this process instance.
|
|
|
|
|
var query = requests.Single(r => r.Url.EndsWith("service/query/tasks"));
|
|
|
|
|
Assert.Equal(HttpMethod.Post, query.Method);
|
|
|
|
|
Assert.Contains("\"processInstanceId\":\"pi-1\"", query.Body);
|
|
|
|
|
Assert.Contains("\"taskDefinitionKey\":\"Beoordelen\"", query.Body);
|
|
|
|
|
// 2. Add teamlead as a candidate group on that task.
|
|
|
|
|
var add = requests.Single(r => r.Method == HttpMethod.Post
|
|
|
|
|
&& r.Url.EndsWith("service/runtime/tasks/task-7/identitylinks"));
|
|
|
|
|
Assert.Contains("\"group\":\"teamlead\"", add.Body);
|
|
|
|
|
Assert.Contains("\"type\":\"candidate\"", add.Body);
|
|
|
|
|
// 3. Remove behandelaar as a candidate group — the task now belongs to teamlead.
|
|
|
|
|
Assert.Contains(requests, r => r.Method == HttpMethod.Delete
|
|
|
|
|
&& r.Url.EndsWith("service/runtime/tasks/task-7/identitylinks/groups/behandelaar/candidate"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task Reassign_is_a_no_op_when_the_beoordelen_task_is_no_longer_open()
|
|
|
|
|
{
|
|
|
|
|
// The behandelaar completed it just before the timer fired: nothing to reassign, no throw.
|
|
|
|
|
var methods = new List<HttpMethod>();
|
|
|
|
|
var client = Client(new StubHandler(req =>
|
|
|
|
|
{
|
|
|
|
|
methods.Add(req.Method);
|
|
|
|
|
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)
|
|
|
|
|
{
|
|
|
|
|
Content = new StringContent("""{"data":[],"total":0}""", Encoding.UTF8, "application/json"),
|
|
|
|
|
});
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
await client.ReassignBeoordelingToTeamleadAsync("pi-1");
|
|
|
|
|
|
|
|
|
|
Assert.DoesNotContain(HttpMethod.Delete, methods);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task Complete_escalation_job_posts_to_the_job_complete_endpoint()
|
|
|
|
|
{
|
|
|
|
|
var capture = new RequestCapture();
|
|
|
|
|
var client = Client(capture.Responds(HttpStatusCode.NoContent));
|
|
|
|
|
|
|
|
|
|
await client.CompleteBeoordelingEscalatieJobAsync("job-9");
|
|
|
|
|
|
|
|
|
|
Assert.Equal(HttpMethod.Post, capture.Seen!.Method);
|
|
|
|
|
Assert.Equal("http://flowable/flowable-rest/external-job-api/acquire/jobs/job-9/complete",
|
|
|
|
|
capture.Seen.RequestUri!.ToString());
|
|
|
|
|
Assert.Contains("\"workerId\":\"worker-x\"", capture.Body);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|