-
-
Notifications
You must be signed in to change notification settings - Fork 149
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
WIP: New "official" incident service example project
- Loading branch information
1 parent
41b104b
commit ba66c36
Showing
18 changed files
with
854 additions
and
0 deletions.
There are no files selected for viewing
26 changes: 26 additions & 0 deletions
26
src/Samples/IncidentService/IncidentService.Tests/IncidentService.Tests.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<IsPackable>false</IsPackable> | ||
<TargetFrameworks>net9.0</TargetFrameworks> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Alba" Version="8.1.1" /> | ||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2"/> | ||
<PackageReference Include="Shouldly" Version="4.2.1" /> | ||
<PackageReference Include="xunit" Version="2.9.0"/> | ||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.0"> | ||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||
<PrivateAssets>all</PrivateAssets> | ||
</PackageReference> | ||
<PackageReference Include="coverlet.collector" Version="3.1.2"> | ||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||
<PrivateAssets>all</PrivateAssets> | ||
</PackageReference> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\IncidentService\IncidentService.csproj" /> | ||
</ItemGroup> | ||
</Project> |
91 changes: 91 additions & 0 deletions
91
src/Samples/IncidentService/IncidentService.Tests/IntegrationContext.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
using Alba; | ||
using Marten; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Oakton; | ||
using Wolverine.Runtime; | ||
using Wolverine.Tracking; | ||
using Xunit; | ||
|
||
namespace IncidentService.Tests; | ||
|
||
public class AppFixture : IAsyncLifetime | ||
{ | ||
public IAlbaHost? Host { get; private set; } | ||
|
||
public async Task InitializeAsync() | ||
{ | ||
OaktonEnvironment.AutoStartHost = true; | ||
|
||
// This is bootstrapping the actual application using | ||
// its implied Program.Main() set up | ||
Host = await AlbaHost.For<Program>(x => { }); | ||
} | ||
|
||
public async Task DisposeAsync() | ||
{ | ||
await Host!.StopAsync(); | ||
Host.Dispose(); | ||
} | ||
} | ||
|
||
[CollectionDefinition("integration")] | ||
public class IntegrationCollection : ICollectionFixture<AppFixture>; | ||
|
||
[Collection("integration")] | ||
public abstract class IntegrationContext : IAsyncLifetime | ||
{ | ||
private readonly AppFixture _fixture; | ||
|
||
protected IntegrationContext(AppFixture fixture) | ||
{ | ||
_fixture = fixture; | ||
Runtime = (WolverineRuntime)fixture.Host!.Services.GetRequiredService<IWolverineRuntime>(); | ||
} | ||
|
||
public WolverineRuntime Runtime { get; } | ||
|
||
public IAlbaHost Host => _fixture.Host!; | ||
public IDocumentStore Store => _fixture.Host!.Services.GetRequiredService<IDocumentStore>(); | ||
|
||
|
||
async Task IAsyncLifetime.InitializeAsync() | ||
{ | ||
// Using Marten, wipe out all data and reset the state | ||
// back to exactly what we described in InitialAccountData | ||
await Store.Advanced.ResetAllData(); | ||
} | ||
|
||
// This is required because of the IAsyncLifetime | ||
// interface. Note that I do *not* tear down database | ||
// state after the test. That's purposeful | ||
public Task DisposeAsync() | ||
{ | ||
return Task.CompletedTask; | ||
} | ||
|
||
public Task<IScenarioResult> Scenario(Action<Scenario> configure) | ||
{ | ||
return Host.Scenario(configure); | ||
} | ||
|
||
// This method allows us to make HTTP calls into our system | ||
// in memory with Alba, but do so within Wolverine's test support | ||
// for message tracking to both record outgoing messages and to ensure | ||
// that any cascaded work spawned by the initial command is completed | ||
// before passing control back to the calling test | ||
protected async Task<(ITrackedSession, IScenarioResult)> TrackedHttpCall(Action<Scenario> configuration) | ||
{ | ||
IScenarioResult result = null!; | ||
|
||
// The outer part is tying into Wolverine's test support | ||
// to "wait" for all detected message activity to complete | ||
var tracked = await Host.ExecuteAndWaitAsync(async () => | ||
{ | ||
// The inner part here is actually making an HTTP request | ||
// to the system under test with Alba | ||
result = await Host.Scenario(configuration); | ||
}); | ||
|
||
return (tracked, result); | ||
} | ||
} |
51 changes: 51 additions & 0 deletions
51
src/Samples/IncidentService/IncidentService.Tests/when_logging_an_incident.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
using Helpdesk.Api.Incidents; | ||
using Shouldly; | ||
using Wolverine.Http; | ||
using Xunit; | ||
|
||
namespace IncidentService.Tests; | ||
|
||
public class when_logging_an_incident : IntegrationContext | ||
{ | ||
public when_logging_an_incident(AppFixture fixture) : base(fixture) | ||
{ | ||
} | ||
|
||
[Fact] | ||
public void unit_test() | ||
{ | ||
var contact = new Contact(ContactChannel.Email); | ||
var command = new LogIncident(Guid.NewGuid(), contact, "It's broken", Guid.NewGuid()); | ||
|
||
var (response, startStream) = LogIncidentEndpoint.Post(command); | ||
|
||
// Should only have the one event | ||
startStream.Events.ShouldBe([ | ||
new IncidentLogged(command.CustomerId, command.Contact, command.Description, command.LoggedBy) | ||
]); | ||
} | ||
|
||
[Fact] | ||
public async Task happy_path_end_to_end() | ||
{ | ||
var contact = new Contact(ContactChannel.Email); | ||
var command = new LogIncident(Guid.NewGuid(), contact, "It's broken", Guid.NewGuid()); | ||
|
||
// Log a new incident first | ||
var initial = await Scenario(x => | ||
{ | ||
x.Post.Json(command).ToUrl("/api/incidents"); | ||
x.StatusCodeShouldBe(201); | ||
}); | ||
|
||
// Read the response body by deserialization | ||
var response = initial.ReadAsJson<CreationResponse<Guid>>(); | ||
|
||
// Reaching into Marten to build the current state of the new Incident | ||
// just to check the expected outcome | ||
using var session = Store.LightweightSession(); | ||
var incident = await session.Events.AggregateStreamAsync<Incident>(response.Value); | ||
|
||
incident.Status.ShouldBe(IncidentStatus.Pending); | ||
} | ||
} |
14 changes: 14 additions & 0 deletions
14
src/Samples/IncidentService/IncidentService/ArchiveIncident.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
using Marten; | ||
|
||
namespace IncidentService; | ||
|
||
public record ArchiveIncident(Guid IncidentId); | ||
|
||
public static class ArchiveIncidentHandler | ||
{ | ||
// Just going to code this one pretty crudely | ||
public static void Handle(ArchiveIncident command, IDocumentSession session) | ||
{ | ||
// TODO -- do more here w/ Jeffry | ||
} | ||
} |
33 changes: 33 additions & 0 deletions
33
src/Samples/IncidentService/IncidentService/CategoriseIncident.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
using Helpdesk.Api.Incidents; | ||
using Microsoft.AspNetCore.Mvc; | ||
using Wolverine.Http; | ||
using Wolverine.Http.Marten; | ||
|
||
namespace IncidentService; | ||
|
||
public record CategoriseIncident( | ||
Guid IncidentId, | ||
IncidentCategory Category, | ||
Guid CategorisedBy, | ||
int Version | ||
); | ||
|
||
public static class CategoriseIncidentEndpoint | ||
{ | ||
public static ProblemDetails Validate(Incident incident) | ||
{ | ||
return incident.Status == IncidentStatus.Closed | ||
? new ProblemDetails { Detail = "Incident is already closed" } | ||
|
||
// All good, keep going! | ||
: WolverineContinue.NoProblems; | ||
} | ||
|
||
[WolverinePost("/api/incidents/{incidentId:guid}/category")] | ||
public static IncidentCategorised Post( | ||
CategoriseIncident command, | ||
[Aggregate("incidentId")] Incident incident, DateTimeOffset now) | ||
{ | ||
return new IncidentCategorised(command.IncidentId, command.Category, command.CategorisedBy); | ||
} | ||
} |
51 changes: 51 additions & 0 deletions
51
src/Samples/IncidentService/IncidentService/CloseIncident.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
using JasperFx.Core; | ||
using Wolverine; | ||
using Wolverine.Http; | ||
using Wolverine.Http.Marten; | ||
using Wolverine.Marten; | ||
|
||
namespace IncidentService; | ||
|
||
public record CloseIncident( | ||
Guid ClosedBy, | ||
int Version | ||
); | ||
|
||
public static class CloseIncidentHandler | ||
{ | ||
[WolverinePost("/api/incidents/close/{id}")] | ||
public static (UpdatedAggregate, Events, OutgoingMessages) Handle( | ||
CloseIncident command, | ||
|
||
[Aggregate] | ||
Incident incident) | ||
{ | ||
/* More logic for later | ||
if (current.Status is not IncidentStatus.ResolutionAcknowledgedByCustomer) | ||
throw new InvalidOperationException("Only incident with acknowledged resolution can be closed"); | ||
if (current.HasOutstandingResponseToCustomer) | ||
throw new InvalidOperationException("Cannot close incident that has outstanding responses to customer"); | ||
*/ | ||
|
||
|
||
if (incident.Status == IncidentStatus.Closed) | ||
{ | ||
return (new UpdatedAggregate(), [], []); | ||
} | ||
|
||
return ( | ||
|
||
// Returning the latest view of | ||
// the Incident as the actual response body | ||
new UpdatedAggregate(), | ||
|
||
// New event to be appended to the Incident stream | ||
[new IncidentClosed(command.ClosedBy)], | ||
|
||
// Getting fancy here, telling Wolverine to schedule a | ||
// command message for three days from now | ||
[new ArchiveIncident(incident.Id).DelayedFor(3.Days())]); | ||
} | ||
} |
12 changes: 12 additions & 0 deletions
12
src/Samples/IncidentService/IncidentService/GetIncidentEndpoint.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
using Wolverine.Http; | ||
|
||
namespace IncidentService; | ||
|
||
public static class GetIncidentEndpoint | ||
{ | ||
[WolverineGet("/api/incidents/{id}")] | ||
public static async Task Get(Guid id) | ||
{ | ||
throw new NotImplementedException(); | ||
} | ||
} |
Oops, something went wrong.