From f929c26d262d7b4b7e417b50fd6040ff073bcc46 Mon Sep 17 00:00:00 2001 From: Dominic Burger Date: Wed, 21 Aug 2024 14:37:59 +0200 Subject: [PATCH 1/4] Return relevant items in STAC search --- .../HttpsStacApiContextFactory.cs | 4 ++++ .../StacServices/StacItemsProvider.cs | 19 ++++++++++++------- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/Geopilot.Api/StacServices/HttpsStacApiContextFactory.cs b/src/Geopilot.Api/StacServices/HttpsStacApiContextFactory.cs index 9b0946c3..2b6b8667 100644 --- a/src/Geopilot.Api/StacServices/HttpsStacApiContextFactory.cs +++ b/src/Geopilot.Api/StacServices/HttpsStacApiContextFactory.cs @@ -1,5 +1,6 @@ using Stac; using Stac.Api.Interfaces; +using Stac.Api.WebApi.Implementations.Default; namespace Geopilot.Api.StacServices; @@ -24,6 +25,9 @@ public HttpsStacApiContextFactory(IHttpContextAccessor httpContextAccessor, ISta public IEnumerable ApplyContextPostQueryFilters(IStacApiContext stacApiContext, IDataProvider dataProvider, IEnumerable items) where T : IStacObject { + // Show the number of items in the search results + stacApiContext.Properties[DefaultConventions.MatchedCountPropertiesKey] = items.Count(); + IEnumerable filteredItems = items; foreach (IStacApiContextFilter stacApiContextFilter in stacApiContextFilterProvider.GetFilters()) { diff --git a/src/Geopilot.Api/StacServices/StacItemsProvider.cs b/src/Geopilot.Api/StacServices/StacItemsProvider.cs index 7c6b7f84..7a58d682 100644 --- a/src/Geopilot.Api/StacServices/StacItemsProvider.cs +++ b/src/Geopilot.Api/StacServices/StacItemsProvider.cs @@ -1,4 +1,5 @@ -using Microsoft.EntityFrameworkCore; +using Geopilot.Api.Models; +using Microsoft.EntityFrameworkCore; using Stac; using Stac.Api.Interfaces; @@ -72,20 +73,24 @@ public string GetItemEtag(string featureId, IStacApiContext stacApiContext) } /// - public Task> GetItemsAsync(IStacApiContext stacApiContext, CancellationToken cancellationToken) + public async Task> GetItemsAsync(IStacApiContext stacApiContext, CancellationToken cancellationToken) { - IEnumerable items = new List(); + var items = new List(); var collectionIds = stacApiContext.Collections?.ToList(); using var db = contextFactory.CreateDbContext(); - var mandates = db.MandatesWithIncludes.AsNoTracking().ToList(); + IEnumerable mandates = await db.MandatesWithIncludes.AsNoTracking().ToListAsync(cancellationToken); if (collectionIds?.Count > 0) { - mandates = mandates.FindAll(m => collectionIds.Contains(stacConverter.GetCollectionId(m))); + mandates = mandates.Where(m => collectionIds.Contains(stacConverter.GetCollectionId(m))); } - mandates.ToList().ForEach(m => items.Concat(m.Deliveries.Select(d => stacConverter.ToStacItem(d)))); - return Task.FromResult(items); + foreach (var mandate in mandates) + { + items.AddRange(mandate.Deliveries.Select(stacConverter.ToStacItem)); + } + + return items; } } From e4e629db7d1cecc85e8f61c5a6a9f02bb0a39bed Mon Sep 17 00:00:00 2001 From: Dominic Burger Date: Mon, 26 Aug 2024 10:21:45 +0200 Subject: [PATCH 2/4] Add integration tests for STAC API --- .../Geopilot.Api.Test.csproj | 3 + tests/Geopilot.Api.Test/GeopilotApiApp.cs | 15 +++++ tests/Geopilot.Api.Test/Program.cs | 42 ++++++++++++++ .../Geopilot.Api.Test/StacApi/CatalogTest.cs | 41 +++++++++++++ .../StacApi/CollectionsTest.cs | 42 ++++++++++++++ .../Geopilot.Api.Test/StacApi/FeaturesTest.cs | 44 ++++++++++++++ tests/Geopilot.Api.Test/StacApi/SearchTest.cs | 57 +++++++++++++++++++ 7 files changed, 244 insertions(+) create mode 100644 tests/Geopilot.Api.Test/GeopilotApiApp.cs create mode 100644 tests/Geopilot.Api.Test/Program.cs create mode 100644 tests/Geopilot.Api.Test/StacApi/CatalogTest.cs create mode 100644 tests/Geopilot.Api.Test/StacApi/CollectionsTest.cs create mode 100644 tests/Geopilot.Api.Test/StacApi/FeaturesTest.cs create mode 100644 tests/Geopilot.Api.Test/StacApi/SearchTest.cs diff --git a/tests/Geopilot.Api.Test/Geopilot.Api.Test.csproj b/tests/Geopilot.Api.Test/Geopilot.Api.Test.csproj index d31dd7d1..f43071d3 100644 --- a/tests/Geopilot.Api.Test/Geopilot.Api.Test.csproj +++ b/tests/Geopilot.Api.Test/Geopilot.Api.Test.csproj @@ -5,9 +5,12 @@ enable enable true + false + CA1861 + diff --git a/tests/Geopilot.Api.Test/GeopilotApiApp.cs b/tests/Geopilot.Api.Test/GeopilotApiApp.cs new file mode 100644 index 00000000..3c42c12a --- /dev/null +++ b/tests/Geopilot.Api.Test/GeopilotApiApp.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; + +namespace Geopilot.Api; + +internal sealed class GeopilotApiApp : WebApplicationFactory +{ + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + base.ConfigureWebHost(builder); + + builder.UseEnvironment("Development"); + builder.UseContentRoot(Directory.GetCurrentDirectory()); + } +} diff --git a/tests/Geopilot.Api.Test/Program.cs b/tests/Geopilot.Api.Test/Program.cs new file mode 100644 index 00000000..a3da6180 --- /dev/null +++ b/tests/Geopilot.Api.Test/Program.cs @@ -0,0 +1,42 @@ +using Geopilot.Api; +using Geopilot.Api.StacServices; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.AspNetCore.StaticFiles; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using Stac.Api.WebApi; + +// This entry point is used by GeopilotApiApp for integration tests. +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddApiVersioning(); +builder.Services.AddCors(); + +builder.Services + .AddControllers() + .ConfigureApplicationPartManager(options => + { + options.ApplicationParts.Add(new AssemblyPart(typeof(Context).Assembly)); + options.ApplicationParts.Add(new AssemblyPart(typeof(StacApiController).Assembly)); + }); + +var contentTypeProvider = new FileExtensionContentTypeProvider(); +contentTypeProvider.Mappings.TryAdd(".log", "text/plain"); +contentTypeProvider.Mappings.TryAdd(".xtf", "application/interlis+xml"); +builder.Services.AddSingleton(contentTypeProvider); + +var factory = new Mock>(); +factory.Setup(f => f.CreateDbContext()).Returns(AssemblyInitialize.DbFixture.GetTestContext); +builder.Services.AddSingleton(factory.Object); +builder.Services.AddTransient((provider) => provider.GetRequiredService>().CreateDbContext()); + +builder.Services.AddStacData(builder => { }); + +var app = builder.Build(); + +app.UseCors(); +app.MapControllers(); + +app.Run(); diff --git a/tests/Geopilot.Api.Test/StacApi/CatalogTest.cs b/tests/Geopilot.Api.Test/StacApi/CatalogTest.cs new file mode 100644 index 00000000..2ba587fc --- /dev/null +++ b/tests/Geopilot.Api.Test/StacApi/CatalogTest.cs @@ -0,0 +1,41 @@ +using Newtonsoft.Json.Schema; +using Stac; +using Stac.Schemas; + +namespace Geopilot.Api.StacApi; + +[TestClass] +public class CatalogTest +{ + private GeopilotApiApp app; + private HttpClient httpClient; + + [TestInitialize] + public void Initialize() + { + app = new GeopilotApiApp(); + httpClient = app.CreateClient(); + } + + [TestCleanup] + public void Cleanup() + { + app.Dispose(); + httpClient.Dispose(); + } + + [TestMethod] + public async Task ValidateCatalogAsync() + { + var stacValidator = new StacValidator(new JSchemaUrlResolver()); + const string catalogUri = "/"; + + var json = await httpClient.GetStringAsync(catalogUri); + + Assert.IsTrue(stacValidator.ValidateJson(json)); + + var catalog = StacConvert.Deserialize(json); + Assert.AreEqual("geopilot", catalog.Id); + Assert.AreEqual("1.0.0", catalog.StacVersion); + } +} diff --git a/tests/Geopilot.Api.Test/StacApi/CollectionsTest.cs b/tests/Geopilot.Api.Test/StacApi/CollectionsTest.cs new file mode 100644 index 00000000..870ce34f --- /dev/null +++ b/tests/Geopilot.Api.Test/StacApi/CollectionsTest.cs @@ -0,0 +1,42 @@ +using Stac.Api.Clients.Collections; + +namespace Geopilot.Api.StacApi; + +[TestClass] +public class CollectionsTest +{ + private GeopilotApiApp app; + private HttpClient httpClient; + private CollectionsClient collectionsClient; + + [TestInitialize] + public void Initialize() + { + app = new GeopilotApiApp(); + httpClient = app.CreateClient(); + collectionsClient = new CollectionsClient(httpClient); + } + + [TestCleanup] + public void Cleanup() + { + app.Dispose(); + httpClient.Dispose(); + } + + [TestMethod] + public async Task GetCollectionsAsync() + { + var collections = await collectionsClient.GetCollectionsAsync(); + + Assert.AreEqual(10, collections.Collections.Count); + } + + [TestMethod] + public async Task DescribeCollectionAsync() + { + var collection = await collectionsClient.DescribeCollectionAsync("coll_1"); + + Assert.AreEqual("Handmade Soft Cheese", collection.Title); + } +} diff --git a/tests/Geopilot.Api.Test/StacApi/FeaturesTest.cs b/tests/Geopilot.Api.Test/StacApi/FeaturesTest.cs new file mode 100644 index 00000000..607f86ef --- /dev/null +++ b/tests/Geopilot.Api.Test/StacApi/FeaturesTest.cs @@ -0,0 +1,44 @@ +using Stac.Api.Clients.Features; + +namespace Geopilot.Api.StacApi; + +[TestClass] +public class FeaturesTest +{ + private GeopilotApiApp app; + private HttpClient httpClient; + private FeaturesClient featuresClient; + + [TestInitialize] + public void Initialize() + { + app = new GeopilotApiApp(); + httpClient = app.CreateClient(); + featuresClient = new FeaturesClient(httpClient); + } + + [TestCleanup] + public void Cleanup() + { + app.Dispose(); + httpClient.Dispose(); + } + + [TestMethod] + public async Task GetFeaturesAsync() + { + var featureCollection = await featuresClient.GetFeaturesAsync("coll_1", null, null, null); + + Assert.AreEqual(2, featureCollection.NumberMatched); + CollectionAssert.AreEqual(new[] { "item_6", "item_14" }, featureCollection.Features.Select(f => f.Id).ToList()); + } + + [TestMethod] + public async Task GetFeatureByIdAsync() + { + var feature = await featuresClient.GetFeatureAsync("coll_1", "item_14"); + + Assert.AreEqual("Datenlieferung_2023-07-04T13:08:47", feature.Title); + Assert.AreEqual(3, feature.Assets.Count); + } +} diff --git a/tests/Geopilot.Api.Test/StacApi/SearchTest.cs b/tests/Geopilot.Api.Test/StacApi/SearchTest.cs new file mode 100644 index 00000000..65d4447a --- /dev/null +++ b/tests/Geopilot.Api.Test/StacApi/SearchTest.cs @@ -0,0 +1,57 @@ +using Stac.Api.Clients.ItemSearch; + +namespace Geopilot.Api.StacApi; + +[TestClass] +public class SearchTest +{ + private GeopilotApiApp app; + private HttpClient httpClient; + private ItemSearchClient searchClient; + + [TestInitialize] + public void Initialize() + { + app = new GeopilotApiApp(); + httpClient = app.CreateClient(); + searchClient = new ItemSearchClient(httpClient); + } + + [TestCleanup] + public void Cleanup() + { + app.Dispose(); + httpClient.Dispose(); + } + + [TestMethod] + public async Task SearchCollection1() + { + var result = await searchClient.PostItemSearchAsync(new SearchBody + { + Collections = new[] { "coll_1" }, + Limit = 12, + }); + + Assert.AreEqual(2, result.Items.Count()); + Assert.AreEqual(2, result.NumberMatched); + Assert.AreEqual(2, result.NumberReturned); + + CollectionAssert.AreEqual(new[] { "item_6", "item_14" }, result.Items.Select(i => i.Id).ToList()); + } + + [TestMethod] + public async Task SearchLimit() + { + const int limit = 8; + var result = await searchClient.PostItemSearchAsync(new SearchBody + { + Collections = Array.Empty(), + Limit = limit, + }); + + Assert.AreEqual(limit, result.Items.Count()); + Assert.AreEqual(20, result.NumberMatched); + Assert.AreEqual(limit, result.NumberReturned); + } +} From 527b9854e13957391e4c2fadeb4d7d01caf9dbfb Mon Sep 17 00:00:00 2001 From: Dominic Burger Date: Mon, 26 Aug 2024 10:37:35 +0200 Subject: [PATCH 3/4] Move NoWarn rule to editorconfig --- tests/.editorconfig | 1 + tests/Geopilot.Api.Test/Geopilot.Api.Test.csproj | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/.editorconfig b/tests/.editorconfig index 33372788..877ea07d 100644 --- a/tests/.editorconfig +++ b/tests/.editorconfig @@ -15,6 +15,7 @@ # Configure code analysis according to current standards by default. [*.cs] dotnet_diagnostic.CA1001.severity = none +dotnet_diagnostic.CA1861.severity = none dotnet_diagnostic.SA0001.severity = none dotnet_diagnostic.CS8604.severity = none dotnet_diagnostic.CS8618.severity = none diff --git a/tests/Geopilot.Api.Test/Geopilot.Api.Test.csproj b/tests/Geopilot.Api.Test/Geopilot.Api.Test.csproj index f43071d3..cd21e792 100644 --- a/tests/Geopilot.Api.Test/Geopilot.Api.Test.csproj +++ b/tests/Geopilot.Api.Test/Geopilot.Api.Test.csproj @@ -6,7 +6,6 @@ enable true false - CA1861 From 37fcf3bf39c621b549b07bf884fbb82735f4e26d Mon Sep 17 00:00:00 2001 From: Dominic Burger Date: Mon, 26 Aug 2024 10:40:08 +0200 Subject: [PATCH 4/4] Remove time from test expectation --- tests/Geopilot.Api.Test/StacApi/FeaturesTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Geopilot.Api.Test/StacApi/FeaturesTest.cs b/tests/Geopilot.Api.Test/StacApi/FeaturesTest.cs index 607f86ef..4ebed95d 100644 --- a/tests/Geopilot.Api.Test/StacApi/FeaturesTest.cs +++ b/tests/Geopilot.Api.Test/StacApi/FeaturesTest.cs @@ -38,7 +38,7 @@ public async Task GetFeatureByIdAsync() { var feature = await featuresClient.GetFeatureAsync("coll_1", "item_14"); - Assert.AreEqual("Datenlieferung_2023-07-04T13:08:47", feature.Title); + StringAssert.StartsWith(feature.Title, "Datenlieferung_2023-07"); Assert.AreEqual(3, feature.Assets.Count); } }