From 2379f0b9b3cc94300ecc882d1745eb35e31ca3c8 Mon Sep 17 00:00:00 2001 From: daplein Date: Fri, 16 Oct 2026 11:23:43 +0100 Subject: [PATCH] Route POST docs/index to document indexing Azure accepts both docs/search.index and docs/index; the Go SDK uses the latter. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../DocumentIndexRouteIntegrationTests.cs | 55 +++++++++++++++++++ .../Controllers/DocumentIndexingController.cs | 6 ++ 2 files changed, 61 insertions(+) create mode 100644 AzureSearchEmulator.IntegrationTests/DocumentIndexRouteIntegrationTests.cs diff --git a/AzureSearchEmulator.IntegrationTests/DocumentIndexRouteIntegrationTests.cs b/AzureSearchEmulator.IntegrationTests/DocumentIndexRouteIntegrationTests.cs new file mode 100644 index 0000000..3989697 --- /dev/null +++ b/AzureSearchEmulator.IntegrationTests/DocumentIndexRouteIntegrationTests.cs @@ -0,0 +1,55 @@ +using System.Net; +using System.Text; +using Azure.Search.Documents.Indexes.Models; +using Xunit; + +namespace AzureSearchEmulator.IntegrationTests; + +/// +/// Azure accepts a document batch on both docs/search.index and docs/index; the +/// Go SDK posts to the latter. Both spellings, in both the path and OData-key forms, must reach +/// the same handler. +/// +public class DocumentIndexRouteIntegrationTests(EmulatorFactory factory) + : IClassFixture +{ + [Theory] + [InlineData("indexes/{0}/docs/index")] + [InlineData("indexes('{0}')/docs/index")] + [InlineData("indexes/{0}/docs/search.index")] + public async Task IndexDocuments_AcceptsBatchOnEitherRoute(string routeFormat) + { + var indexName = "docs-index-route-" + Guid.NewGuid().ToString("N")[..8]; + var cancellationToken = TestContext.Current.CancellationToken; + + var indexClient = factory.CreateSearchIndexClient(); + await indexClient.CreateIndexAsync( + new SearchIndex(indexName) + { + Fields = + { + new SearchField("id", SearchFieldDataType.String) { IsKey = true }, + new SearchField("name", SearchFieldDataType.String) { IsSearchable = true }, + } + }, + cancellationToken); + + using var http = factory.CreateHttpClient(); + + var response = await http.PostAsync( + string.Format(routeFormat, indexName) + "?api-version=2024-07-01", + new StringContent( + """{ "value": [ { "@search.action": "upload", "id": "1", "name": "hello" } ] }""", + Encoding.UTF8, + "application/json"), + cancellationToken); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var searchClient = factory.CreateSearchClient(indexName); + var document = await searchClient.GetDocumentAsync>("1", cancellationToken: cancellationToken); + Assert.Equal("hello", document.Value["name"].ToString()); + + await indexClient.DeleteIndexAsync(indexName, cancellationToken); + } +} diff --git a/AzureSearchEmulator/Controllers/DocumentIndexingController.cs b/AzureSearchEmulator/Controllers/DocumentIndexingController.cs index 8faa27a..c54ee8b 100644 --- a/AzureSearchEmulator/Controllers/DocumentIndexingController.cs +++ b/AzureSearchEmulator/Controllers/DocumentIndexingController.cs @@ -22,9 +22,15 @@ public class DocumentIndexingController( /// private const int MaxLoggedBodyLength = 4096; + /// + /// Azure accepts both docs/search.index and the shorter docs/index for a batch; + /// the Go and Java SDKs post to the latter. + /// [HttpPost] [Route("indexes({indexKey})/docs/search.index")] [Route("indexes/{indexKey}/docs/search.index")] + [Route("indexes({indexKey})/docs/index")] + [Route("indexes/{indexKey}/docs/index")] public async Task IndexDocuments(string indexKey) { // Strip quotes that may be captured from OData-style URLs