Skip to content

Instantly share code, notes, and snippets.

@jezzsantos
Created June 26, 2021 09:03
Show Gist options
  • Save jezzsantos/9fc1b79bea7d3cab2de0032c118b7df2 to your computer and use it in GitHub Desktop.
Save jezzsantos/9fc1b79bea7d3cab2de0032c118b7df2 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()}/";
this.webHost = WebHost.CreateDefaultBuilder(null)
.UseModularStartup<TStartup>()
.UseUrls(ServiceUrl)
.UseKestrel()
.ConfigureLogging((ctx, builder) => builder.AddConsole())
.Build();
this.webHost.Start();
this.container = HostContext.Container;
Api = ApiClient.Create(ServiceUrl);
}
public JsonServiceClient Api { get; }
private string ServiceUrl { get; }
public void Dispose()
{
this.webHost?.StopAsync().GetAwaiter().GetResult();
this.webHost?.Dispose();
}
public TService Resolve<TService>()
{
return this.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>
Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Availabilities.Api", "Availabilities.Api\Availabilities.Api.csproj", "{9312AF09-C149-46CF-BC9F-3C3DB31F3558}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Availabilities.Api.IntegrationTests", "Availabilities.Api.IntegrationTests\Availabilities.Api.IntegrationTests.csproj", "{9D1E1C55-EBFB-48BF-A78A-AEA4F19008ED}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{9312AF09-C149-46CF-BC9F-3C3DB31F3558}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9312AF09-C149-46CF-BC9F-3C3DB31F3558}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9312AF09-C149-46CF-BC9F-3C3DB31F3558}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9312AF09-C149-46CF-BC9F-3C3DB31F3558}.Release|Any CPU.Build.0 = Release|Any CPU
{9D1E1C55-EBFB-48BF-A78A-AEA4F19008ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9D1E1C55-EBFB-48BF-A78A-AEA4F19008ED}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9D1E1C55-EBFB-48BF-A78A-AEA4F19008ED}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9D1E1C55-EBFB-48BF-A78A-AEA4F19008ED}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
using System;
using Availabilities.Apis.ServiceOperations.Availabilities;
using Availabilities.Apis.ServiceOperations.Bookings;
using Availabilities.Apis.Validators;
using Availabilities.Other;
using Availabilities.Resources;
using Availabilities.Storage;
using FluentAssertions;
using Xunit;
namespace Availabilities.Api.IntegrationTests.Services
{
[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 = this.setup.Api.Get(new GetAllAvailabilitiesRequest())
.Availabilities;
availabilities.Should().HaveCount(1);
availabilities[0].StartUtc.Should().Be(Validations.Availabilities.MinimumAvailability);
availabilities[0].EndUtc.Should().Be(Validations.Availabilities.MaximumAvailability);
}
[Fact]
public void WhenGetAllAvailabilitiesAndBookingCreated_ThenReturnsBothSlotsOrdered()
{
var start = DateTime.UtcNow.ToNearestQuarterHour();
var end = start.AnHourLater();
this.setup.Api.Post(new CreateBookingRequest
{
StartUtc = start,
EndUtc = end,
Description = "adescription1"
});
var availabilities = this.setup.Api.Get(new GetAllAvailabilitiesRequest())
.Availabilities;
availabilities.Should().HaveCount(2);
availabilities[0].StartUtc.Should().Be(Validations.Availabilities.MinimumAvailability);
availabilities[0].EndUtc.Should().Be(start);
availabilities[1].StartUtc.Should().Be(end);
availabilities[1].EndUtc.Should().Be(Validations.Availabilities.MaximumAvailability);
}
[Fact]
public void WhenGetAllAvailabilitiesAndAdjacentBookingsCreated_ThenReturnsBothSlotsOrdered()
{
var start1 = DateTime.UtcNow.ToNearestQuarterHour();
var end1 = start1.AnHourLater();
this.setup.Api.Post(new CreateBookingRequest
{
StartUtc = start1,
EndUtc = end1,
Description = "adescription1"
});
var start2 = end1;
var end2 = start2.AnHourLater();
this.setup.Api.Post(new CreateBookingRequest
{
StartUtc = start2,
EndUtc = end2,
Description = "adescription2"
});
var availabilities = this.setup.Api.Get(new GetAllAvailabilitiesRequest())
.Availabilities;
availabilities.Should().HaveCount(2);
availabilities[0].StartUtc.Should().Be(Validations.Availabilities.MinimumAvailability);
availabilities[0].EndUtc.Should().Be(start1);
availabilities[1].StartUtc.Should().Be(end2);
availabilities[1].EndUtc.Should().Be(Validations.Availabilities.MaximumAvailability);
}
[Fact]
public void WhenGetAllAvailabilitiesAndDetachedBookingsCreated_ThenReturnsAllThreeSlotsOrdered()
{
var start1 = DateTime.UtcNow.ToNearestQuarterHour();
var end1 = start1.AnHourLater();
this.setup.Api.Post(new CreateBookingRequest
{
StartUtc = start1,
EndUtc = end1,
Description = "adescription1"
});
var start2 = end1.AnHourLater();
var end2 = start2.AnHourLater();
this.setup.Api.Post(new CreateBookingRequest
{
StartUtc = start2,
EndUtc = end2,
Description = "adescription2"
});
var availabilities = this.setup.Api.Get(new GetAllAvailabilitiesRequest())
.Availabilities;
availabilities.Should().HaveCount(3);
availabilities[0].StartUtc.Should().Be(Validations.Availabilities.MinimumAvailability);
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(Validations.Availabilities.MaximumAvailability);
}
[Fact]
public void WhenGetAllAvailabilitiesAndDeleteCreatedBooking_ThenReturnsOneSlot()
{
var start = DateTime.UtcNow.ToNextQuarterHour();
var end = start.AnHourLater();
var booking = this.setup.Api.Post(new CreateBookingRequest
{
StartUtc = start,
EndUtc = end,
Description = "adescription1"
}).Booking;
this.setup.Api.Delete(new DeleteBookingRequest
{
Id = booking.Id
});
var availabilities = this.setup.Api.Get(new GetAllAvailabilitiesRequest())
.Availabilities;
availabilities.Should().HaveCount(1);
availabilities[0].StartUtc.Should().Be(Validations.Availabilities.MinimumAvailability);
availabilities[0].EndUtc.Should().Be(Validations.Availabilities.MaximumAvailability);
}
[Fact]
public void WhenGetAllAvailabilitiesAndDeleteDetachedCreatedBookings_ThenReturnsOneSlot()
{
var start1 = DateTime.UtcNow.ToNextQuarterHour();
var end1 = start1.AnHourLater();
var booking1 = this.setup.Api.Post(new CreateBookingRequest
{
StartUtc = start1,
EndUtc = end1,
Description = "adescription1"
}).Booking;
var start2 = start1.AddDays(1);
var end2 = start2.AnHourLater();
var booking2 = this.setup.Api.Post(new CreateBookingRequest
{
StartUtc = start2,
EndUtc = end2,
Description = "adescription1"
}).Booking;
this.setup.Api.Delete(new DeleteBookingRequest
{
Id = booking1.Id
});
this.setup.Api.Delete(new DeleteBookingRequest
{
Id = booking2.Id
});
var availabilities = this.setup.Api.Get(new GetAllAvailabilitiesRequest())
.Availabilities;
availabilities.Should().HaveCount(1);
availabilities[0].StartUtc.Should().Be(Validations.Availabilities.MinimumAvailability);
availabilities[0].EndUtc.Should().Be(Validations.Availabilities.MaximumAvailability);
}
}
}
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.Services
{
[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();
this.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();
this.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);
this.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);
this.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 = this.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();
this.setup.Api.Post(new CreateBookingRequest
{
StartUtc = start,
EndUtc = end,
Description = "adescription1"
});
this.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();
this.setup.Api.Post(new CreateBookingRequest
{
StartUtc = start,
EndUtc = end,
Description = "adescription1"
});
this.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();
this.setup.Api.Post(new CreateBookingRequest
{
StartUtc = start,
EndUtc = end,
Description = "adescription1"
});
this.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();
this.setup.Api.Post(new CreateBookingRequest
{
StartUtc = start,
EndUtc = end,
Description = "adescription1"
});
this.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();
this.setup.Api.Post(new CreateBookingRequest
{
StartUtc = start,
EndUtc = end,
Description = "adescription1"
});
this.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 = this.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 = this.setup.Api.Post(new CreateBookingRequest
{
StartUtc = start,
EndUtc = end,
Description = "adescription1"
}).Booking;
var bookings = this.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 = this.setup.Api.Post(new CreateBookingRequest
{
StartUtc = start,
EndUtc = end,
Description = "adescription1"
}).Booking;
var booking2 = this.setup.Api.Post(new CreateBookingRequest
{
StartUtc = end,
EndUtc = end.AnHourLater(),
Description = "adescription2"
}).Booking;
var bookings = this.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