From 8dc8f55eea6a08fef09c4f878d02f779281cda55 Mon Sep 17 00:00:00 2001 From: aaron burtle Date: Mon, 6 Apr 2026 10:44:46 -0700 Subject: [PATCH 1/5] move determination of put patch semantics for propper error messaging --- .../RestApiTests/Patch/PatchApiTestBase.cs | 31 +++++++++++++++++++ .../RestApiTests/Put/PutApiTestBase.cs | 31 +++++++++++++++++++ src/Service/Controllers/RestController.cs | 9 ++++-- 3 files changed, 69 insertions(+), 2 deletions(-) diff --git a/src/Service.Tests/SqlTests/RestApiTests/Patch/PatchApiTestBase.cs b/src/Service.Tests/SqlTests/RestApiTests/Patch/PatchApiTestBase.cs index 90e132a652..084ef16588 100644 --- a/src/Service.Tests/SqlTests/RestApiTests/Patch/PatchApiTestBase.cs +++ b/src/Service.Tests/SqlTests/RestApiTests/Patch/PatchApiTestBase.cs @@ -485,6 +485,37 @@ await SetupAndRunRestApiTest( ); } + /// + /// Tests that a PATCH request with an invalid If-Match header value + /// (anything other than "*") returns a 400 Bad Request response + /// because ETags are not supported. + /// + [TestMethod] + public virtual async Task PatchOne_Update_InvalidIfMatchHeader_Returns400_Test() + { + Dictionary headerDictionary = new(); + headerDictionary.Add("If-Match", "\"abc123\""); + string requestBody = @" + { + ""title"": ""The Hobbit Returns to The Shire"", + ""publisher_id"": 1234 + }"; + + await SetupAndRunRestApiTest( + primaryKeyRoute: "id/1", + queryString: null, + entityNameOrPath: _integrationEntityName, + sqlQuery: string.Empty, + operationType: EntityActionOperation.UpsertIncremental, + headers: new HeaderDictionary(headerDictionary), + requestBody: requestBody, + exceptionExpected: true, + expectedErrorMessage: "Etags not supported, use '*'", + expectedStatusCode: HttpStatusCode.BadRequest, + expectedSubStatusCode: DataApiBuilderException.SubStatusCodes.BadRequest.ToString() + ); + } + /// /// Test to validate successful execution of PATCH operation which satisfies the database policy for the update operation it resolves into. /// diff --git a/src/Service.Tests/SqlTests/RestApiTests/Put/PutApiTestBase.cs b/src/Service.Tests/SqlTests/RestApiTests/Put/PutApiTestBase.cs index 5ee3654897..9989eac099 100644 --- a/src/Service.Tests/SqlTests/RestApiTests/Put/PutApiTestBase.cs +++ b/src/Service.Tests/SqlTests/RestApiTests/Put/PutApiTestBase.cs @@ -1114,6 +1114,37 @@ await SetupAndRunRestApiTest( ); } + /// + /// Tests that a PUT request with an invalid If-Match header value + /// (anything other than "*") returns a 400 Bad Request response + /// because ETags are not supported. + /// + [TestMethod] + public virtual async Task PutOne_Update_InvalidIfMatchHeader_Returns400_Test() + { + Dictionary headerDictionary = new(); + headerDictionary.Add("If-Match", "\"abc123\""); + string requestBody = @" + { + ""title"": ""The Return of the King"", + ""publisher_id"": 1234 + }"; + + await SetupAndRunRestApiTest( + primaryKeyRoute: "id/1", + queryString: null, + entityNameOrPath: _integrationEntityName, + sqlQuery: string.Empty, + operationType: EntityActionOperation.Upsert, + headers: new HeaderDictionary(headerDictionary), + requestBody: requestBody, + exceptionExpected: true, + expectedErrorMessage: "Etags not supported, use '*'", + expectedStatusCode: HttpStatusCode.BadRequest, + expectedSubStatusCode: DataApiBuilderException.SubStatusCodes.BadRequest.ToString() + ); + } + /// /// Tests that a PUT request with If-Match header (strict update semantics) /// still requires a primary key route. When If-Match is present, the operation diff --git a/src/Service/Controllers/RestController.cs b/src/Service/Controllers/RestController.cs index 48a1b98e49..9666de0f30 100644 --- a/src/Service/Controllers/RestController.cs +++ b/src/Service/Controllers/RestController.cs @@ -161,7 +161,7 @@ public async Task Upsert( { return await HandleOperation( route, - DeterminePatchPutSemantics(EntityActionOperation.Upsert)); + EntityActionOperation.Upsert); } /// @@ -181,7 +181,7 @@ public async Task UpsertIncremental( { return await HandleOperation( route, - DeterminePatchPutSemantics(EntityActionOperation.UpsertIncremental)); + EntityActionOperation.UpsertIncremental); } /// @@ -204,6 +204,11 @@ private async Task HandleOperation( try { + if (operationType is EntityActionOperation.Upsert or EntityActionOperation.UpsertIncremental) + { + operationType = DeterminePatchPutSemantics(operationType); + } + TelemetryMetricsHelper.IncrementActiveRequests(ApiType.REST); if (activity is not null) From 32a1bd66b5780d1d27614add4d395046bbb543e8 Mon Sep 17 00:00:00 2001 From: aaronburtle <93220300+aaronburtle@users.noreply.github.com> Date: Mon, 6 Apr 2026 11:18:21 -0700 Subject: [PATCH 2/5] Update src/Service/Controllers/RestController.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Service/Controllers/RestController.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Service/Controllers/RestController.cs b/src/Service/Controllers/RestController.cs index 9666de0f30..e967d4fe68 100644 --- a/src/Service/Controllers/RestController.cs +++ b/src/Service/Controllers/RestController.cs @@ -204,13 +204,12 @@ private async Task HandleOperation( try { + TelemetryMetricsHelper.IncrementActiveRequests(ApiType.REST); + if (operationType is EntityActionOperation.Upsert or EntityActionOperation.UpsertIncremental) { operationType = DeterminePatchPutSemantics(operationType); } - - TelemetryMetricsHelper.IncrementActiveRequests(ApiType.REST); - if (activity is not null) { activity.TrackMainControllerActivityStarted( From f870ea20ef30014bc814902c1523cb8681f9aa48 Mon Sep 17 00:00:00 2001 From: aaron burtle Date: Mon, 6 Apr 2026 11:55:30 -0700 Subject: [PATCH 3/5] retrigger CI From af44839e049b08e8dbf4881411bfcabf72b19b3d Mon Sep 17 00:00:00 2001 From: aaron burtle Date: Mon, 6 Apr 2026 12:25:07 -0700 Subject: [PATCH 4/5] format --- src/Service/Controllers/RestController.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Service/Controllers/RestController.cs b/src/Service/Controllers/RestController.cs index e967d4fe68..8f83e2d493 100644 --- a/src/Service/Controllers/RestController.cs +++ b/src/Service/Controllers/RestController.cs @@ -210,6 +210,7 @@ private async Task HandleOperation( { operationType = DeterminePatchPutSemantics(operationType); } + if (activity is not null) { activity.TrackMainControllerActivityStarted( From c3e2f3861c9abe644a0699ddc98a394b2835d725 Mon Sep 17 00:00:00 2001 From: aaron burtle Date: Mon, 6 Apr 2026 15:05:43 -0700 Subject: [PATCH 5/5] whitespace --- src/Service/Controllers/RestController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Service/Controllers/RestController.cs b/src/Service/Controllers/RestController.cs index 8f83e2d493..2fadc636d0 100644 --- a/src/Service/Controllers/RestController.cs +++ b/src/Service/Controllers/RestController.cs @@ -210,7 +210,7 @@ private async Task HandleOperation( { operationType = DeterminePatchPutSemantics(operationType); } - + if (activity is not null) { activity.TrackMainControllerActivityStarted(