using System.Text; using System.Text.Json; using Database.Connection; using Features.Locations.Repository; using FluentAssertions; using Microsoft.Extensions.DependencyInjection; using Reqnroll; namespace API.Specs.Steps; [Binding] public class BrewerySteps(ScenarioContext scenario) : ApiStepsBase(scenario) { private const string AccessTokenKey = "accessToken"; private const string CityIdKey = "cityId"; private const string BreweryPostIdKey = "breweryPostId"; private const string BreweryRowVersionKey = "breweryRowVersion"; private const string FixtureCountryName = "Fixture Country"; private const string FixtureCountryIsoCode = "FX"; private const string FixtureStateProvinceName = "Fixture State"; private const string FixtureStateProvinceIsoCode = "FX-TS"; private const string FixtureCityName = "Fixture City"; private HttpRequestMessage NewAuthenticatedRequest(HttpMethod method, string url) { HttpRequestMessage requestMessage = new(method, url); if (Scenario.TryGetValue(AccessTokenKey, out string? accessToken)) requestMessage.Headers.Add("Authorization", $"Bearer {accessToken}"); return requestMessage; } private async Task SendAsync(HttpRequestMessage requestMessage) { HttpClient client = GetClient(); HttpResponseMessage response = await client.SendAsync(requestMessage); string responseBody = await response.Content.ReadAsStringAsync(); Scenario[ResponseKey] = response; Scenario[ResponseBodyKey] = responseBody; } [Given("a city exists")] public async Task GivenACityExists() { ISqlConnectionFactory connectionFactory = GetFactory() .Services.GetRequiredService(); LocationRepository repository = new(connectionFactory); Guid countryId = await repository.GetCountryIdAsync(FixtureCountryIsoCode) ?? await repository.CreateCountryAsync(FixtureCountryName, FixtureCountryIsoCode); Guid stateProvinceId = await repository.GetStateProvinceIdAsync(FixtureStateProvinceIsoCode) ?? await repository.CreateStateProvinceAsync( FixtureStateProvinceName, FixtureStateProvinceIsoCode, countryId ); Guid cityId = await repository.GetCityIdAsync(FixtureCityName, FixtureStateProvinceIsoCode) ?? await repository.CreateCityAsync(FixtureCityName, stateProvinceId); Scenario[CityIdKey] = cityId; } private async Task CreateBreweryAsync( string breweryName, string description, string addressLine1, string postalCode, Guid cityId ) { var createData = new { breweryName, description, location = new { cityId, addressLine1, addressLine2 = (string?)null, postalCode, coordinates = (object?)null, }, }; HttpRequestMessage requestMessage = NewAuthenticatedRequest( HttpMethod.Post, "/api/brewery" ); requestMessage.Content = new StringContent( JsonSerializer.Serialize(createData), Encoding.UTF8, "application/json" ); await SendAsync(requestMessage); } [When("I create a brewery with values:")] public async Task WhenICreateABreweryWithValues(Table table) { DataTableRow row = table.Rows[0]; Guid cityId = Scenario.TryGetValue(CityIdKey, out Guid id) ? id : Guid.NewGuid(); await CreateBreweryAsync( row["BreweryName"] ?? "", row["Description"] ?? "", row["AddressLine1"] ?? "", row["PostalCode"] ?? "", cityId ); } [When("I create a brewery with a non-existent city")] public async Task WhenICreateABreweryWithANonExistentCity() { await CreateBreweryAsync( "Ghost City Brewery", "A brewery in a city that does not exist", "1 Nowhere St", "00000", Guid.NewGuid() ); } [Given("I have created a brewery")] public async Task GivenIHaveCreatedABrewery() { Guid cityId = Scenario.TryGetValue(CityIdKey, out Guid id) ? id : throw new InvalidOperationException("city ID not found in scenario"); await CreateBreweryAsync( "Test Brewery", "A test brewery", "123 Test St", "00000", cityId ); Scenario .TryGetValue(ResponseKey, out HttpResponseMessage? response) .Should() .BeTrue(); ((int)response!.StatusCode).Should().Be(201, "fixture brewery creation must succeed"); Scenario.TryGetValue(ResponseBodyKey, out string? responseBody).Should().BeTrue(); using JsonDocument doc = JsonDocument.Parse(responseBody!); JsonElement payload = doc.RootElement.GetProperty("payload"); Scenario[BreweryPostIdKey] = payload.GetProperty("breweryPostId").GetGuid(); Scenario[BreweryRowVersionKey] = payload.GetProperty("rowVersion").GetString() ?? throw new InvalidOperationException("rowVersion missing from created brewery"); } private Guid GetCreatedBreweryPostId() { return Scenario.TryGetValue(BreweryPostIdKey, out Guid id) ? id : throw new InvalidOperationException("brewery post ID not found in scenario"); } [When("I retrieve the brewery by ID")] public async Task WhenIRetrieveTheBreweryById() { HttpRequestMessage requestMessage = NewAuthenticatedRequest( HttpMethod.Get, $"/api/brewery/{GetCreatedBreweryPostId()}" ); await SendAsync(requestMessage); } [When("I retrieve a brewery by a non-existent ID")] public async Task WhenIRetrieveABreweryByANonExistentId() { HttpRequestMessage requestMessage = NewAuthenticatedRequest( HttpMethod.Get, $"/api/brewery/{Guid.NewGuid()}" ); await SendAsync(requestMessage); } [When("I update the brewery with values:")] public async Task WhenIUpdateTheBreweryWithValues(Table table) { DataTableRow row = table.Rows[0]; Guid breweryPostId = GetCreatedBreweryPostId(); string rowVersion = Scenario.TryGetValue(BreweryRowVersionKey, out string? rv) ? rv : throw new InvalidOperationException("row version not found in scenario"); var updateData = new { breweryPostId, rowVersion, breweryName = row["BreweryName"] ?? "", description = row["Description"] ?? "", location = (object?)null, }; HttpRequestMessage requestMessage = NewAuthenticatedRequest( HttpMethod.Put, $"/api/brewery/{breweryPostId}" ); requestMessage.Content = new StringContent( JsonSerializer.Serialize(updateData), Encoding.UTF8, "application/json" ); await SendAsync(requestMessage); } [When("I update a non-existent brewery")] public async Task WhenIUpdateANonExistentBrewery() { Guid breweryPostId = Guid.NewGuid(); var updateData = new { breweryPostId, rowVersion = Convert.ToBase64String([0x00, 0x00, 0x00, 0x00]), breweryName = "Nonexistent", description = "Nonexistent", location = (object?)null, }; HttpRequestMessage requestMessage = NewAuthenticatedRequest( HttpMethod.Put, $"/api/brewery/{breweryPostId}" ); requestMessage.Content = new StringContent( JsonSerializer.Serialize(updateData), Encoding.UTF8, "application/json" ); await SendAsync(requestMessage); } [When("I delete the brewery")] public async Task WhenIDeleteTheBrewery() { HttpRequestMessage requestMessage = NewAuthenticatedRequest( HttpMethod.Delete, $"/api/brewery/{GetCreatedBreweryPostId()}" ); await SendAsync(requestMessage); } [When("I delete a non-existent brewery")] public async Task WhenIDeleteANonExistentBrewery() { HttpRequestMessage requestMessage = NewAuthenticatedRequest( HttpMethod.Delete, $"/api/brewery/{Guid.NewGuid()}" ); await SendAsync(requestMessage); } [Given("I am logged in as a different user")] public async Task GivenIAmLoggedInAsADifferentUser() { HttpClient client = GetClient(); string suffix = Guid.NewGuid().ToString("N")[..8]; var registrationData = new { username = $"otheruser-{suffix}", firstName = "Other", lastName = "User", email = $"otheruser-{suffix}@example.com", dateOfBirth = "1990-01-01", password = "Password1!", }; HttpRequestMessage requestMessage = new(HttpMethod.Post, "/api/auth/register") { Content = new StringContent( JsonSerializer.Serialize(registrationData), Encoding.UTF8, "application/json" ), }; HttpResponseMessage response = await client.SendAsync(requestMessage); response.EnsureSuccessStatusCode(); string responseBody = await response.Content.ReadAsStringAsync(); using JsonDocument doc = JsonDocument.Parse(responseBody); JsonElement payload = doc.RootElement.GetProperty("payload"); string accessToken = ( payload.TryGetProperty("accessToken", out JsonElement tokenElem) ? tokenElem.GetString() : null ) ?? throw new InvalidOperationException("accessToken missing from registration payload"); Scenario[AccessTokenKey] = accessToken; } [Then("retrieving the brewery by ID should now return HTTP status {int}")] public async Task ThenRetrievingTheBreweryByIdShouldNowReturnHttpStatus(int expectedCode) { HttpRequestMessage requestMessage = NewAuthenticatedRequest( HttpMethod.Get, $"/api/brewery/{GetCreatedBreweryPostId()}" ); await SendAsync(requestMessage); Scenario .TryGetValue(ResponseKey, out HttpResponseMessage? response) .Should() .BeTrue(); ((int)response!.StatusCode).Should().Be(expectedCode); } }