Skip to content

Instantly share code, notes, and snippets.

@jezzsantos
Created June 26, 2021 04:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jezzsantos/7db509963a174668607a2070fff539ba to your computer and use it in GitHub Desktop.
Save jezzsantos/7db509963a174668607a2070fff539ba to your computer and use it in GitHub Desktop.
Availabilities.IntegrationTests
using ServiceStack;
namespace Availabilities.Api.IntegrationTests
{
public static class ApiClient
{
public static JsonServiceClient Create(string serviceUrl)
{
return new JsonServiceClient($"{serviceUrl.WithTrailingSlash()}api/");
}
}
}
using System;
using System.Net;
using System.Net.Sockets;
using Funq;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Logging;
using ServiceStack;
namespace Availabilities.Api.IntegrationTests
{
public class ApiSpecSetup<TStartup> : IDisposable where TStartup : class
{
private readonly Container container;
private readonly IWebHost webHost;
public ApiSpecSetup()
{
ServiceStackHost.Instance?.Dispose();
ServiceUrl = $"https://localhost:{GetNextAvailablePort()}/";
webHost = WebHost.CreateDefaultBuilder(null)
.UseModularStartup<TStartup>()
.UseUrls(ServiceUrl)
.UseKestrel()
.ConfigureLogging((ctx, builder) => builder.AddConsole())
.Build();
webHost.Start();
container = HostContext.Container;
Api = ApiClient.Create(ServiceUrl);
}
public JsonServiceClient Api { get; }
private string ServiceUrl { get; }
public void Dispose()
{
webHost?.StopAsync().GetAwaiter().GetResult();
webHost?.Dispose();
}
public TService Resolve<TService>()
{
return container.Resolve<TService>();
}
private static int GetNextAvailablePort()
{
var listener = new TcpListener(IPAddress.Loopback, 0);
listener.Start();
var port = ((IPEndPoint) listener.LocalEndpoint).Port;
listener.Stop();
return port;
}
}
}
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
}
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="5.10.3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="xunit" Version="2.4.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
<PackageReference Include="coverlet.collector" Version="1.2.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Availabilities.Api\Availabilities.Api.csproj" />
</ItemGroup>
</Project>
using System;
using Availabilities.Apis.ServiceOperations.Availabilities;
using Availabilities.Apis.ServiceOperations.Bookings;
using Availabilities.Other;
using Availabilities.Resources;
using Availabilities.Storage;
using FluentAssertions;
using Xunit;
namespace Availabilities.Api.IntegrationTests.Apis
{
[Trait("Category", "Integration")]
[Collection("ThisAssembly")]
public class AvailabilitiesApiSpec : IClassFixture<ApiSpecSetup<TestStartup>>
{
private readonly ApiSpecSetup<TestStartup> setup;
public AvailabilitiesApiSpec(ApiSpecSetup<TestStartup> setup)
{
this.setup = setup;
this.setup.Resolve<IStorage<Booking>>().DestroyAll();
this.setup.Resolve<IStorage<Availability>>().DestroyAll();
}
[Fact]
public void WhenGetAllAvailabilitiesAndNoBookings_ThenReturnsOneSlot()
{
var availabilities = setup.Api.Get(new GetAllAvailabilitiesRequest())
.Availabilities;
availabilities.Should().HaveCount(1);
availabilities[0].StartUtc.Should().Be(DateTime.MinValue);
availabilities[0].EndUtc.Should().Be(DateTime.MaxValue);
}
[Fact]
public void WhenGetAllAvailabilitiesAndBookingCreated_ThenReturnsBothSlotsOrdered()
{
var start = DateTime.UtcNow.ToNearestQuarterHour();
var end = start.AnHourLater();
setup.Api.Post(new CreateBookingRequest
{
StartUtc = start,
EndUtc = end,
Description = "adescription1"
});
var availabilities = setup.Api.Get(new GetAllAvailabilitiesRequest())
.Availabilities;
availabilities.Should().HaveCount(2);
availabilities[0].StartUtc.Should().Be(DateTime.MinValue);
availabilities[0].EndUtc.Should().Be(start);
availabilities[1].StartUtc.Should().Be(end);
availabilities[1].EndUtc.Should().Be(DateTime.MaxValue);
}
[Fact]
public void WhenGetAllAvailabilitiesAndAdjacentBookingsCreated_ThenReturnsBothSlotsOrdered()
{
var start1 = DateTime.UtcNow.ToNearestQuarterHour();
var end1 = start1.AnHourLater();
setup.Api.Post(new CreateBookingRequest
{
StartUtc = start1,
EndUtc = end1,
Description = "adescription1"
});
var start2 = end1;
var end2 = start2.AnHourLater();
setup.Api.Post(new CreateBookingRequest
{
StartUtc = start2,
EndUtc = end2,
Description = "adescription2"
});
var availabilities = setup.Api.Get(new GetAllAvailabilitiesRequest())
.Availabilities;
availabilities.Should().HaveCount(2);
availabilities[0].StartUtc.Should().Be(DateTime.MinValue);
availabilities[0].EndUtc.Should().Be(start1);
availabilities[1].StartUtc.Should().Be(end2);
availabilities[1].EndUtc.Should().Be(DateTime.MaxValue);
}
[Fact]
public void WhenGetAllAvailabilitiesAndDetachedBookingsCreated_ThenReturnsAllThreeSlotsOrdered()
{
var start1 = DateTime.UtcNow.ToNearestQuarterHour();
var end1 = start1.AnHourLater();
setup.Api.Post(new CreateBookingRequest
{
StartUtc = start1,
EndUtc = end1,
Description = "adescription1"
});
var start2 = end1.AnHourLater();
var end2 = start2.AnHourLater();
setup.Api.Post(new CreateBookingRequest
{
StartUtc = start2,
EndUtc = end2,
Description = "adescription2"
});
var availabilities = setup.Api.Get(new GetAllAvailabilitiesRequest())
.Availabilities;
availabilities.Should().HaveCount(3);
availabilities[0].StartUtc.Should().Be(DateTime.MinValue);
availabilities[0].EndUtc.Should().Be(start1);
availabilities[1].StartUtc.Should().Be(end1);
availabilities[1].EndUtc.Should().Be(start2);
availabilities[2].StartUtc.Should().Be(end2);
availabilities[2].EndUtc.Should().Be(DateTime.MaxValue);
}
}
}
using System;
using System.Net;
using Availabilities.Apis.ServiceOperations.Bookings;
using Availabilities.Apis.Validators;
using Availabilities.Other;
using Availabilities.Resources;
using Availabilities.Storage;
using FluentAssertions;
using ServiceStack;
using Xunit;
namespace Availabilities.Api.IntegrationTests.Apis
{
[Trait("Category", "Integration")]
[Collection("ThisAssembly")]
public class BookingsApiSpec : IClassFixture<ApiSpecSetup<TestStartup>>
{
private readonly ApiSpecSetup<TestStartup> setup;
public BookingsApiSpec(ApiSpecSetup<TestStartup> setup)
{
this.setup = setup;
this.setup.Resolve<IStorage<Booking>>().DestroyAll();
this.setup.Resolve<IStorage<Availability>>().DestroyAll();
}
[Fact]
public void WhenCreateBookingAndStartsInPast_ThenThrows()
{
var start = DateTime.UtcNow.SubtractMinutes(1);
var end = start.AnHourLater();
setup.Api
.Invoking(x => x.Post(new CreateBookingRequest
{
StartUtc = start,
EndUtc = end,
Description = "adescription"
}))
.Should().Throw<WebServiceException>()
.Which.HasStatus(HttpStatusCode.BadRequest);
}
[Fact]
public void WhenCreateBookingAndStartsAfterEnds_ThenThrows()
{
var start = DateTime.UtcNow.FiveMinutesLater();
var end = start.AnHourEarlier();
setup.Api
.Invoking(x => x.Post(new CreateBookingRequest
{
StartUtc = start,
EndUtc = end,
Description = "adescription"
}))
.Should().Throw<WebServiceException>()
.Which.HasStatus(HttpStatusCode.BadRequest);
}
[Fact]
public void WhenCreateBookingAndTooShort_ThenThrows()
{
var start = DateTime.UtcNow.FiveMinutesLater();
var end = start.SubtractMinutes(Validations.Bookings.MinimumBookingLengthInMinutes + 1);
setup.Api
.Invoking(x => x.Post(new CreateBookingRequest
{
StartUtc = start,
EndUtc = end,
Description = "adescription"
}))
.Should().Throw<WebServiceException>()
.Which.HasStatus(HttpStatusCode.BadRequest);
}
[Fact]
public void WhenCreateBookingAndTooLong_ThenThrows()
{
var start = DateTime.UtcNow.FiveMinutesLater();
var end = start.AddMinutes(Validations.Bookings.MaximumBookingLengthInMinutes + 1);
setup.Api
.Invoking(x => x.Post(new CreateBookingRequest
{
StartUtc = start,
EndUtc = end,
Description = "adescription"
}))
.Should().Throw<WebServiceException>()
.Which.HasStatus(HttpStatusCode.BadRequest);
}
[Fact]
public void WhenCreateBookingAndAllAvailability_ThenCreatesBookingToNearestQuarterHour()
{
var start = DateTime.UtcNow.FiveMinutesLater();
var end = start.AnHourLater();
var booking = setup.Api.Post(new CreateBookingRequest
{
StartUtc = start,
EndUtc = end,
Description = "adescription"
}).Booking;
booking.Id.Should().NotBeEmpty();
booking.StartUtc.Should().Be(start.ToNearestQuarterHour());
booking.EndUtc.Should().Be(end.ToNearestQuarterHour());
booking.Description.Should().Be("adescription");
}
[Fact]
public void WhenCreateBookingAndExactlyNoAvailability_ThenThrows()
{
var start = DateTime.UtcNow.FiveMinutesLater();
var end = start.AnHourLater();
setup.Api.Post(new CreateBookingRequest
{
StartUtc = start,
EndUtc = end,
Description = "adescription1"
});
setup.Api
.Invoking(x => x.Post(new CreateBookingRequest
{
StartUtc = start,
EndUtc = end,
Description = "adescription2"
}))
.Should().Throw<WebServiceException>()
.Which.HasStatus(HttpStatusCode.BadRequest);
}
[Fact]
public void WhenCreateBookingAndWithinNoAvailability_ThenThrows()
{
var start = DateTime.UtcNow.ToNextQuarterHour();
var end = start.AnHourLater();
setup.Api.Post(new CreateBookingRequest
{
StartUtc = start,
EndUtc = end,
Description = "adescription1"
});
setup.Api
.Invoking(x => x.Post(new CreateBookingRequest
{
StartUtc = start.FiveMinutesLater(),
EndUtc = end.FiveMinutesEarlier(),
Description = "adescription2"
}))
.Should().Throw<WebServiceException>()
.Which.HasStatus(HttpStatusCode.BadRequest);
}
[Fact]
public void WhenCreateBookingAndOutsideNoAvailability_ThenThrows()
{
var start = DateTime.UtcNow.ToNextQuarterHour();
var end = start.AnHourLater();
setup.Api.Post(new CreateBookingRequest
{
StartUtc = start,
EndUtc = end,
Description = "adescription1"
});
setup.Api
.Invoking(x => x.Post(new CreateBookingRequest
{
StartUtc = start.FiveMinutesEarlier(),
EndUtc = end.FiveMinutesLater(),
Description = "adescription2"
}))
.Should().Throw<WebServiceException>()
.Which.HasStatus(HttpStatusCode.BadRequest);
}
[Fact]
public void WhenCreateBookingAndNoAvailabilityAtStart_ThenThrows()
{
var start = DateTime.UtcNow.ToNextQuarterHour();
var end = start.AnHourLater();
setup.Api.Post(new CreateBookingRequest
{
StartUtc = start,
EndUtc = end,
Description = "adescription1"
});
setup.Api
.Invoking(x => x.Post(new CreateBookingRequest
{
StartUtc = start.FiveMinutesLater(),
EndUtc = end.FiveMinutesEarlier(),
Description = "adescription2"
}))
.Should().Throw<WebServiceException>()
.Which.HasStatus(HttpStatusCode.BadRequest);
}
[Fact]
public void WhenCreateBookingAndNoAvailabilityAtEnd_ThenThrows()
{
var start = DateTime.UtcNow.FiveMinutesLater();
var end = start.AnHourLater();
setup.Api.Post(new CreateBookingRequest
{
StartUtc = start,
EndUtc = end,
Description = "adescription1"
});
setup.Api
.Invoking(x => x.Post(new CreateBookingRequest
{
StartUtc = start.FiveMinutesEarlier(),
EndUtc = end.FiveMinutesEarlier(),
Description = "adescription2"
}))
.Should().Throw<WebServiceException>()
.Which.HasStatus(HttpStatusCode.BadRequest);
}
[Fact]
public void WhenGetAllBookingsAndNoBookings_ThenReturnsNone()
{
var bookings = setup.Api.Get(new GetAllBookingsRequest())
.Bookings;
bookings.Should().HaveCount(0);
}
[Fact]
public void WhenGetAllBookingsAndSingleBooking_ThenReturnsOne()
{
var start = DateTime.UtcNow.FiveMinutesLater();
var end = start.AnHourLater();
var booking = setup.Api.Post(new CreateBookingRequest
{
StartUtc = start,
EndUtc = end,
Description = "adescription1"
}).Booking;
var bookings = setup.Api.Get(new GetAllBookingsRequest())
.Bookings;
bookings.Should().HaveCount(1);
bookings[0].Id.Should().Be(booking.Id);
bookings[0].StartUtc.Should().Be(booking.StartUtc);
bookings[0].EndUtc.Should().Be(booking.EndUtc);
bookings[0].Description.Should().Be(booking.Description);
}
[Fact]
public void WhenGetAllBookingsAndAdjacentBookings_ThenReturnsBothOrdered()
{
var start = DateTime.UtcNow.ToNextQuarterHour();
var end = start.AnHourLater();
var booking1 = setup.Api.Post(new CreateBookingRequest
{
StartUtc = start,
EndUtc = end,
Description = "adescription1"
}).Booking;
var booking2 = setup.Api.Post(new CreateBookingRequest
{
StartUtc = end,
EndUtc = end.AnHourLater(),
Description = "adescription2"
}).Booking;
var bookings = setup.Api.Get(new GetAllBookingsRequest())
.Bookings;
bookings.Should().HaveCount(2);
bookings[0].Id.Should().Be(booking1.Id);
bookings[1].Id.Should().Be(booking2.Id);
}
}
}
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using ServiceStack;
using ServiceStack.Configuration;
namespace Availabilities.Api.IntegrationTests
{
public class TestStartup : ModularStartup
{
public new void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
var appSettings = new NetCoreAppSettings(Configuration);
app.UseServiceStack(new ServiceHost
{
BeforeConfigure =
{
host =>
{
var container = host.GetContainer();
container.AddSingleton<IAppSettings>(appSettings);
host.AppSettings = appSettings;
}
},
AfterConfigure =
{
host =>
{
// Override services for testing
host.Config.DebugMode = true;
}
}
});
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment