From 380cd0325c4cc87047cefc23e1b9a071346c45ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Tom=C3=A1s?= Date: Fri, 9 Jun 2017 12:16:57 +0200 Subject: [PATCH] Added location.api integration tests --- docker-compose-windows.override.yml | 17 +++- docker-compose-windows.yml | 13 ++- .../Controllers/LocationsController.cs | 23 +++-- .../Infrastructure/LocationsContextSeed.cs | 82 ++++++++++------ .../Repositories/ILocationsRepository.cs | 8 +- .../Repositories/LocationsRepository.cs | 37 +++----- .../Services/ILocationsService.cs | 2 + .../Services/LocationsService.cs | 41 ++++---- .../Location/Locations.API/Model/Locations.cs | 18 +++- .../Locations.API/Model/UserLocation.cs | 9 +- .../Locations/LocationsScenarioBase.cs | 7 +- .../Services/Locations/LocationsScenarios.cs | 95 ++++++++++++++++++- 12 files changed, 250 insertions(+), 102 deletions(-) diff --git a/docker-compose-windows.override.yml b/docker-compose-windows.override.yml index c3733c164..45b2db748 100644 --- a/docker-compose-windows.override.yml +++ b/docker-compose-windows.override.yml @@ -80,4 +80,19 @@ services: - SA_PASSWORD=Pass@word - ACCEPT_EULA=Y ports: - - "5433:1433" \ No newline at end of file + - "5433:1433" + + nosql.data: + ports: + - "27017:27017" + + locations.api: + environment: + - ASPNETCORE_ENVIRONMENT=Development + - ASPNETCORE_URLS=http://0.0.0.0:80 + - ConnectionString=mongodb://nosql.data + - Database=LocationsDb + - identityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105. + - EventBusConnection=rabbitmq + ports: + - "5109:80" \ No newline at end of file diff --git a/docker-compose-windows.yml b/docker-compose-windows.yml index 48f7ef7b4..2caf209ab 100644 --- a/docker-compose-windows.yml +++ b/docker-compose-windows.yml @@ -53,9 +53,20 @@ services: - ordering.api - identity.api - basket.api - + + locations.api: + image: locations.api + build: + context: ./src/Services/Location/Locations.API + dockerfile: Dockerfile + depends_on: + - nosql.data + sql.data: image: microsoft/mssql-server-windows + + nosql.data: + image: mongo:windowsservercore basket.data: image: redis:nanoserver diff --git a/src/Services/Location/Locations.API/Controllers/LocationsController.cs b/src/Services/Location/Locations.API/Controllers/LocationsController.cs index fed584f6b..2d8b73e3c 100644 --- a/src/Services/Location/Locations.API/Controllers/LocationsController.cs +++ b/src/Services/Location/Locations.API/Controllers/LocationsController.cs @@ -20,8 +20,8 @@ namespace Locations.API.Controllers _identityService = identityService ?? throw new ArgumentNullException(nameof(identityService)); } - //GET api/v1/[controller]/1 - [Route("{userId:int}")] + //GET api/v1/[controller]/user/1 + [Route("user/{userId:int}")] [HttpGet] public async Task GetUserLocation(int userId) { @@ -29,19 +29,28 @@ namespace Locations.API.Controllers return Ok(userLocation); } - //GET api/v1/[controller]/locations - [Route("locations")] + //GET api/v1/[controller]/ + [Route("")] [HttpGet] public async Task GetAllLocations() { - var userLocation = await _locationsService.GetAllLocation(); - return Ok(userLocation); + var locations = await _locationsService.GetAllLocation(); + return Ok(locations); + } + + //GET api/v1/[controller]/1 + [Route("{locationId}")] + [HttpGet] + public async Task GetLocation(string locationId) + { + var location = await _locationsService.GetLocation(locationId); + return Ok(location); } //POST api/v1/[controller]/ [Route("")] [HttpPost] - public async Task UpdateUserLocation([FromBody]LocationRequest newLocReq) + public async Task CreateOrUpdateUserLocation([FromBody]LocationRequest newLocReq) { var userId = _identityService.GetUserIdentity(); var result = await _locationsService.AddOrUpdateUserLocation(userId, newLocReq); diff --git a/src/Services/Location/Locations.API/Infrastructure/LocationsContextSeed.cs b/src/Services/Location/Locations.API/Infrastructure/LocationsContextSeed.cs index 950698d97..05acfe0b8 100644 --- a/src/Services/Location/Locations.API/Infrastructure/LocationsContextSeed.cs +++ b/src/Services/Location/Locations.API/Infrastructure/LocationsContextSeed.cs @@ -5,7 +5,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; - using MongoDB.Bson; using MongoDB.Driver; using MongoDB.Driver.GeoJsonObjectModel; using System.Collections.Generic; @@ -25,7 +24,7 @@ { await SetIndexes(); await SetUSLocations(); - } + } } static async Task SetUSLocations() @@ -36,11 +35,12 @@ Description = "United States" }; us.SetLocation(-101.357386, 41.650455); - await ctx.Locations.InsertOneAsync(us); + us.SetArea(GetUSPoligon()); + await ctx.Locations.InsertOneAsync(us); await SetWashingtonLocations(us.Id); } - static async Task SetWashingtonLocations(ObjectId parentId) + static async Task SetWashingtonLocations(string parentId) { var wht = new Locations() { @@ -49,34 +49,35 @@ Description = "Washington" }; wht.SetLocation(-119.542781, 47.223652); + wht.SetArea(GetWashingtonPoligon()); await ctx.Locations.InsertOneAsync(wht); await SetSeattleLocations(wht.Id); await SetRedmondLocations(wht.Id); } - static async Task SetSeattleLocations(ObjectId parentId) + static async Task SetSeattleLocations(string parentId) { var stl = new Locations() { Parent_Id = parentId, Code = "SEAT", - Description = "Seattle", - Polygon = GetSeattlePoligon() + Description = "Seattle" }; + stl.SetArea(GetSeattlePoligon()); stl.SetLocation(-122.330747, 47.603111); await ctx.Locations.InsertOneAsync(stl); } - static async Task SetRedmondLocations(ObjectId parentId) + static async Task SetRedmondLocations(string parentId) { var rdm = new Locations() { Parent_Id = parentId, Code = "REDM", - Description = "Redmond", - Polygon = GetRedmondPoligon() + Description = "Redmond" }; rdm.SetLocation(-122.122887, 47.674961); + rdm.SetArea(GetRedmondPoligon()); await ctx.Locations.InsertOneAsync(rdm); } @@ -86,13 +87,24 @@ var builder = Builders.IndexKeys; var keys = builder.Geo2DSphere(prop => prop.Location); await ctx.Locations.Indexes.CreateOneAsync(keys); - } + } + + static List GetUSPoligon() + { + return new List() + { + new GeoJson2DGeographicCoordinates(-62.88205, 48.7985), + new GeoJson2DGeographicCoordinates(-129.3132, 48.76513), + new GeoJson2DGeographicCoordinates(-120.9496, 30.12256), + new GeoJson2DGeographicCoordinates(-111.3944, 30.87114), + new GeoJson2DGeographicCoordinates(-78.11975, 24.24979), + new GeoJson2DGeographicCoordinates(-62.88205, 48.7985) + }; + } - static GeoJsonPolygon GetSeattlePoligon() + static List GetSeattlePoligon() { - return new GeoJsonPolygon(new GeoJsonPolygonCoordinates( - new GeoJsonLinearRingCoordinates( - new List() + return new List() { new GeoJson2DGeographicCoordinates(-122.36238,47.82929), new GeoJson2DGeographicCoordinates(-122.42091,47.6337), @@ -100,24 +112,34 @@ new GeoJson2DGeographicCoordinates(-122.20788,47.50259), new GeoJson2DGeographicCoordinates(-122.26934,47.73644), new GeoJson2DGeographicCoordinates(-122.36238,47.82929) - }))); + }; } - static GeoJsonPolygon GetRedmondPoligon() + static List GetRedmondPoligon() { - return new GeoJsonPolygon(new GeoJsonPolygonCoordinates( - new GeoJsonLinearRingCoordinates( - new List() + return new List() { - new GeoJson2DGeographicCoordinates(47.73148, -122.15432), - new GeoJson2DGeographicCoordinates(47.72559, -122.17673), - new GeoJson2DGeographicCoordinates(47.67851, -122.16904), - new GeoJson2DGeographicCoordinates(47.65036, -122.16136), - new GeoJson2DGeographicCoordinates(47.62746, -122.15604), - new GeoJson2DGeographicCoordinates(47.63463, -122.01562), - new GeoJson2DGeographicCoordinates(47.74244, -122.04961), - new GeoJson2DGeographicCoordinates(47.73148, -122.15432), - }))); - } + new GeoJson2DGeographicCoordinates(-122.15432, 47.73148), + new GeoJson2DGeographicCoordinates(-122.17673, 47.72559), + new GeoJson2DGeographicCoordinates(-122.16904, 47.67851), + new GeoJson2DGeographicCoordinates(-122.16136, 47.65036), + new GeoJson2DGeographicCoordinates(-122.15604, 47.62746), + new GeoJson2DGeographicCoordinates(-122.01562, 47.63463), + new GeoJson2DGeographicCoordinates(-122.04961, 47.74244), + new GeoJson2DGeographicCoordinates(-122.15432, 47.73148) + }; + } + + static List GetWashingtonPoligon() + { + return new List() + { + new GeoJson2DGeographicCoordinates(-124.68633, 48.8943), + new GeoJson2DGeographicCoordinates(-124.32962, 45.66613), + new GeoJson2DGeographicCoordinates(-116.73824, 45.93384), + new GeoJson2DGeographicCoordinates(-116.96912, 49.04282), + new GeoJson2DGeographicCoordinates(-124.68633, 48.8943) + }; + } } } diff --git a/src/Services/Location/Locations.API/Infrastructure/Repositories/ILocationsRepository.cs b/src/Services/Location/Locations.API/Infrastructure/Repositories/ILocationsRepository.cs index a0657e35d..91cb2c258 100644 --- a/src/Services/Location/Locations.API/Infrastructure/Repositories/ILocationsRepository.cs +++ b/src/Services/Location/Locations.API/Infrastructure/Repositories/ILocationsRepository.cs @@ -1,21 +1,19 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Repositories { using Microsoft.eShopOnContainers.Services.Locations.API.Model; - using MongoDB.Bson; + using ViewModel; using System.Collections.Generic; using System.Threading.Tasks; public interface ILocationsRepository { - Task GetAsync(ObjectId locationId); + Task GetAsync(string locationId); Task> GetLocationListAsync(); Task GetUserLocationAsync(int userId); - Task> GetNearestLocationListAsync(double lat, double lon); - - Task GetLocationByCurrentAreaAsync(Locations location); + Task> GetCurrentUserRegionsListAsync(LocationRequest currentPosition); Task AddUserLocationAsync(UserLocation location); diff --git a/src/Services/Location/Locations.API/Infrastructure/Repositories/LocationsRepository.cs b/src/Services/Location/Locations.API/Infrastructure/Repositories/LocationsRepository.cs index bb0039fd7..0d756ab5f 100644 --- a/src/Services/Location/Locations.API/Infrastructure/Repositories/LocationsRepository.cs +++ b/src/Services/Location/Locations.API/Infrastructure/Repositories/LocationsRepository.cs @@ -1,17 +1,14 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Repositories { - using System; - using System.Threading.Tasks; - using Microsoft.eShopOnContainers.Services.Locations.API.Model; using Microsoft.EntityFrameworkCore; - using System.Collections.Generic; - using System.Globalization; - using System.Linq; + using Microsoft.eShopOnContainers.Services.Locations.API.Model; using Microsoft.Extensions.Options; + using MongoDB.Bson; using MongoDB.Driver; using MongoDB.Driver.GeoJsonObjectModel; - using MongoDB.Driver.Builders; - using MongoDB.Bson; + using System.Collections.Generic; + using System.Threading.Tasks; + using ViewModel; public class LocationsRepository : ILocationsRepository @@ -23,9 +20,9 @@ _context = new LocationsContext(settings); } - public async Task GetAsync(ObjectId locationId) + public async Task GetAsync(string locationId) { - var filter = Builders.Filter.Eq("Id", locationId); + var filter = Builders.Filter.Eq("Id", ObjectId.Parse(locationId)); return await _context.Locations .Find(filter) .FirstOrDefaultAsync(); @@ -42,20 +39,16 @@ public async Task> GetLocationListAsync() { return await _context.Locations.Find(new BsonDocument()).ToListAsync(); - } + } - public async Task> GetNearestLocationListAsync(double lat, double lon) + public async Task> GetCurrentUserRegionsListAsync(LocationRequest currentPosition) { - var point = GeoJson.Point(GeoJson.Geographic(lon, lat)); - var query = new FilterDefinitionBuilder().Near(x => x.Location, point); - return await _context.Locations.Find(query).ToListAsync(); - } - - public async Task GetLocationByCurrentAreaAsync(Locations location) - { - var query = new FilterDefinitionBuilder().GeoIntersects("Location", location.Polygon); - return await _context.Locations.Find(query).FirstOrDefaultAsync(); - } + var point = GeoJson.Point(GeoJson.Geographic(currentPosition.Longitude, currentPosition.Latitude)); + var orderByDistanceQuery = new FilterDefinitionBuilder().Near(x => x.Location, point); + var withinAreaQuery = new FilterDefinitionBuilder().GeoIntersects("Polygon", point); + var filter = Builders.Filter.And(orderByDistanceQuery, withinAreaQuery); + return await _context.Locations.Find(filter).ToListAsync(); + } public async Task AddUserLocationAsync(UserLocation location) { diff --git a/src/Services/Location/Locations.API/Infrastructure/Services/ILocationsService.cs b/src/Services/Location/Locations.API/Infrastructure/Services/ILocationsService.cs index 98f2904e1..c0e50169f 100644 --- a/src/Services/Location/Locations.API/Infrastructure/Services/ILocationsService.cs +++ b/src/Services/Location/Locations.API/Infrastructure/Services/ILocationsService.cs @@ -7,6 +7,8 @@ public interface ILocationsService { + Task GetLocation(string locationId); + Task GetUserLocation(int id); Task> GetAllLocation(); diff --git a/src/Services/Location/Locations.API/Infrastructure/Services/LocationsService.cs b/src/Services/Location/Locations.API/Infrastructure/Services/LocationsService.cs index 81f89b9d6..eaf89c03a 100644 --- a/src/Services/Location/Locations.API/Infrastructure/Services/LocationsService.cs +++ b/src/Services/Location/Locations.API/Infrastructure/Services/LocationsService.cs @@ -18,6 +18,11 @@ _locationsRepository = locationsRepository ?? throw new ArgumentNullException(nameof(locationsRepository)); } + public async Task GetLocation(string locationId) + { + return await _locationsRepository.GetAsync(locationId); + } + public async Task GetUserLocation(int id) { return await _locationsRepository.GetUserLocationAsync(id); @@ -30,40 +35,28 @@ public async Task AddOrUpdateUserLocation(string id, LocationRequest currentPosition) { - Locations currentUserAreaLocation = null; - if (!int.TryParse(id, out int userId)) { throw new ArgumentException("Not valid userId"); } - // Get the nearest locations ordered - var nearestLocationList = await _locationsRepository.GetNearestLocationListAsync(currentPosition.Latitude, currentPosition.Longitude); - - // Check out in which region we currently are - foreach(var locationCandidate in nearestLocationList.Where(x=> x.Polygon != null)) - { - currentUserAreaLocation = await _locationsRepository.GetLocationByCurrentAreaAsync(locationCandidate); - if(currentUserAreaLocation != null) { break; } - } - - if(currentUserAreaLocation is null) + // Get the list of ordered regions the user currently is within + var currentUserAreaLocationList = await _locationsRepository.GetCurrentUserRegionsListAsync(currentPosition); + + if(currentUserAreaLocationList is null) { throw new LocationDomainException("User current area not found"); } // If current area found, then update user location - if(currentUserAreaLocation != null) - { - var locationAncestors = new List(); - var userLocation = await _locationsRepository.GetUserLocationAsync(userId); - userLocation = userLocation ?? new UserLocation(); - userLocation.UserId = userId; - userLocation.LocationId = currentUserAreaLocation.Id; - userLocation.UpdateDate = DateTime.UtcNow; - await _locationsRepository.UpdateUserLocationAsync(userLocation); - } - + var locationAncestors = new List(); + var userLocation = await _locationsRepository.GetUserLocationAsync(userId); + userLocation = userLocation ?? new UserLocation(); + userLocation.UserId = userId; + userLocation.LocationId = currentUserAreaLocationList[0].Id; + userLocation.UpdateDate = DateTime.UtcNow; + await _locationsRepository.UpdateUserLocationAsync(userLocation); + return true; } } diff --git a/src/Services/Location/Locations.API/Model/Locations.cs b/src/Services/Location/Locations.API/Model/Locations.cs index e35221bce..0944008a7 100644 --- a/src/Services/Location/Locations.API/Model/Locations.cs +++ b/src/Services/Location/Locations.API/Model/Locations.cs @@ -3,18 +3,22 @@ using MongoDB.Bson; using MongoDB.Driver.GeoJsonObjectModel; using System.Collections.Generic; + using MongoDB.Bson.Serialization.Attributes; public class Locations { - public ObjectId Id { get; set; } + [BsonRepresentation(BsonType.ObjectId)] + public string Id { get; set; } public string Code { get; set; } - public ObjectId Parent_Id { get; set; } + [BsonRepresentation(BsonType.ObjectId)] + public string Parent_Id { get; set; } public string Description { get; set; } public double Latitude { get; set; } public double Longitude { get; set; } - public GeoJsonPoint Location { get; set; } - public GeoJsonPolygon Polygon { get; set; } + public GeoJsonPoint Location { get; private set; } + public GeoJsonPolygon Polygon { get; private set; } public void SetLocation(double lon, double lat) => SetPosition(lon, lat); + public void SetArea(List coordinatesList) => SetPolygon(coordinatesList); private void SetPosition(double lon, double lat) { @@ -23,5 +27,11 @@ Location = new GeoJsonPoint( new GeoJson2DGeographicCoordinates(lon, lat)); } + + private void SetPolygon(List coordinatesList) + { + Polygon = new GeoJsonPolygon(new GeoJsonPolygonCoordinates( + new GeoJsonLinearRingCoordinates(coordinatesList))); + } } } diff --git a/src/Services/Location/Locations.API/Model/UserLocation.cs b/src/Services/Location/Locations.API/Model/UserLocation.cs index ca60661c2..e72d3e26e 100644 --- a/src/Services/Location/Locations.API/Model/UserLocation.cs +++ b/src/Services/Location/Locations.API/Model/UserLocation.cs @@ -1,16 +1,17 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API.Model { - using MongoDB.Bson.Serialization.Attributes; using MongoDB.Bson; + using MongoDB.Bson.Serialization.Attributes; using System; - using System.Collections.Generic; public class UserLocation { [BsonIgnoreIfDefault] - public ObjectId Id { get; set; } + [BsonRepresentation(BsonType.ObjectId)] + public string Id { get; set; } public int UserId { get; set; } = 0; - public ObjectId LocationId { get; set; } + [BsonRepresentation(BsonType.ObjectId)] + public string LocationId { get; set; } public DateTime UpdateDate { get; set; } } } diff --git a/test/Services/IntegrationTests/Services/Locations/LocationsScenarioBase.cs b/test/Services/IntegrationTests/Services/Locations/LocationsScenarioBase.cs index b9d3af44c..be3b23803 100644 --- a/test/Services/IntegrationTests/Services/Locations/LocationsScenarioBase.cs +++ b/test/Services/IntegrationTests/Services/Locations/LocationsScenarioBase.cs @@ -22,10 +22,15 @@ namespace IntegrationTests.Services.Locations { public static string Locations = "api/v1/locations"; - public static string LocationBy(int id) + public static string LocationBy(string id) { return $"api/v1/locations/{id}"; } + + public static string UserLocationBy(int id) + { + return $"api/v1/locations/user/{id}"; + } } public static class Post diff --git a/test/Services/IntegrationTests/Services/Locations/LocationsScenarios.cs b/test/Services/IntegrationTests/Services/Locations/LocationsScenarios.cs index 34b043909..63437e7dc 100644 --- a/test/Services/IntegrationTests/Services/Locations/LocationsScenarios.cs +++ b/test/Services/IntegrationTests/Services/Locations/LocationsScenarios.cs @@ -1,5 +1,7 @@ using Microsoft.eShopOnContainers.Services.Locations.API.Model; using Microsoft.eShopOnContainers.Services.Locations.API.ViewModel; +using Location = Microsoft.eShopOnContainers.Services.Locations.API.Model.Locations; +using System.Collections.Generic; using Newtonsoft.Json; using System.Net.Http; using System.Text; @@ -19,20 +21,107 @@ namespace IntegrationTests.Services.Locations var userId = 1234; var content = new StringContent(BuildLocationsRequest(-122.315752, 47.604610), UTF8Encoding.UTF8, "application/json"); + // Expected result + var expectedLocation = "SEAT"; + + // Act var response = await server.CreateClient() .PostAsync(Post.AddNewLocation, content); - response.EnsureSuccessStatusCode(); + var userLocationResponse = await server.CreateClient() + .GetAsync(Get.UserLocationBy(userId)); + + var responseBody = await userLocationResponse.Content.ReadAsStringAsync(); + var userLocation = JsonConvert.DeserializeObject(responseBody); + + var locationResponse = await server.CreateClient() + .GetAsync(Get.LocationBy(userLocation.LocationId)); + + responseBody = await locationResponse.Content.ReadAsStringAsync(); + var location = JsonConvert.DeserializeObject(responseBody); + + // Assert + Assert.Equal(expectedLocation, location.Code); + } + } + + [Fact] + public async Task Set_new_user_readmond_location_response_ok_status_code() + { + using (var server = CreateServer()) + { + var userId = 1234; + var content = new StringContent(BuildLocationsRequest(-122.119998, 47.690876), UTF8Encoding.UTF8, "application/json"); + + // Expected result + var expectedLocation = "REDM"; + + // Act + var response = await server.CreateClient() + .PostAsync(Post.AddNewLocation, content); + + var userLocationResponse = await server.CreateClient() + .GetAsync(Get.UserLocationBy(userId)); + + var responseBody = await userLocationResponse.Content.ReadAsStringAsync(); + var userLocation = JsonConvert.DeserializeObject(responseBody); + + var locationResponse = await server.CreateClient() + .GetAsync(Get.LocationBy(userLocation.LocationId)); + + responseBody = await locationResponse.Content.ReadAsStringAsync(); + var location = JsonConvert.DeserializeObject(responseBody); + + // Assert + Assert.Equal(expectedLocation, location.Code); + } + } + + [Fact] + public async Task Set_new_user_Washington_location_response_ok_status_code() + { + using (var server = CreateServer()) + { + var userId = 1234; + var content = new StringContent(BuildLocationsRequest(-121.040360, 48.091631), UTF8Encoding.UTF8, "application/json"); + + // Expected result + var expectedLocation = "WHT"; + + // Act + var response = await server.CreateClient() + .PostAsync(Post.AddNewLocation, content); var userLocationResponse = await server.CreateClient() - .GetAsync(Get.LocationBy(userId)); + .GetAsync(Get.UserLocationBy(userId)); var responseBody = await userLocationResponse.Content.ReadAsStringAsync(); var userLocation = JsonConvert.DeserializeObject(responseBody); - response.EnsureSuccessStatusCode(); + var locationResponse = await server.CreateClient() + .GetAsync(Get.LocationBy(userLocation.LocationId)); + + responseBody = await locationResponse.Content.ReadAsStringAsync(); + var location = JsonConvert.DeserializeObject(responseBody); + + // Assert + Assert.Equal(expectedLocation, location.Code); + } + } + + [Fact] + public async Task Get_all_locations_response_ok_status_code() + { + using (var server = CreateServer()) + { + var response = await server.CreateClient() + .GetAsync(Get.Locations); + var responseBody = await response.Content.ReadAsStringAsync(); + var locations = JsonConvert.DeserializeObject>(responseBody); + // Assert + Assert.NotEmpty(locations); } }