diff --git a/csharp/Svix.Tests/WiremockTests.cs b/csharp/Svix.Tests/WiremockTests.cs index 72fd10887..da8b0a223 100644 --- a/csharp/Svix.Tests/WiremockTests.cs +++ b/csharp/Svix.Tests/WiremockTests.cs @@ -361,5 +361,16 @@ public void ClientProvidedIdempotencyKeyIsNotOverridden() ); Assert.Equal(1, stub.LogEntries.Count); } + + [Fact] + public void UnknownKeysAreIgnored() + { + var res = + """{"data": [],"iterator": "iterator","prevIterator": "-iterator","done": true,"extra-field": "ignored"}"""; + stub.Given(Request.Create().WithPath("/api/v1/app")) + .RespondWith(Response.Create().WithStatusCode(200).WithBody(res)); + + client.Application.List(); + } } } diff --git a/go/svix_test.go b/go/svix_test.go index 2bd359e98..46254df67 100644 --- a/go/svix_test.go +++ b/go/svix_test.go @@ -749,3 +749,20 @@ func TestReadStructEnumField(t *testing.T) { } } + +func TestUnknownKeysAreIgnored(t *testing.T) { + ctx := context.Background() + svx := newMockClient() + httpmock.Activate() + defer httpmock.DeactivateAndReset() + httpmock.RegisterResponder("GET", "http://testapi.test/api/v1/app", + func(r *http.Request) (*http.Response, error) { + return httpmock.NewStringResponse(200, `{"data":[],"done":true,"iterator":null,"prevIterator":null,"extra-key":"ignored"}`), nil + }, + ) + _, err := svx.Application.List(ctx, nil) + if err != nil { + t.Error(err) + } + +} diff --git a/java/lib/src/main/java/com/svix/Utils.java b/java/lib/src/main/java/com/svix/Utils.java index 9d7e45999..629b3a39b 100644 --- a/java/lib/src/main/java/com/svix/Utils.java +++ b/java/lib/src/main/java/com/svix/Utils.java @@ -1,6 +1,7 @@ package com.svix; import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; @@ -53,6 +54,7 @@ public static ObjectMapper getObjectMapper() { ObjectMapper mapper = new ObjectMapper(); mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); mapper.enable(JsonParser.Feature.INCLUDE_SOURCE_IN_LOCATION); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); mapper.registerModule(new JavaTimeModule()); mapper.registerModule(new Jdk8Module()); return mapper; diff --git a/java/lib/src/test/com/svix/test/WiremockTests.java b/java/lib/src/test/com/svix/test/WiremockTests.java index 6948be32f..29bca090e 100644 --- a/java/lib/src/test/com/svix/test/WiremockTests.java +++ b/java/lib/src/test/com/svix/test/WiremockTests.java @@ -544,4 +544,17 @@ public void testClientProvidedIdempotencyKeyIsNotOverridden() throws Exception { .withHeader("idempotency-key", equalTo(clientProvidedKey))); } + @Test + public void testUnknownKeysAreIgnored() throws Exception { + String res = "{\"data\": [],\"iterator\": \"iterator\",\"prevIterator\": \"-iterator\",\"done\": true,\"extra-field\": \"ignored\"}"; + Svix svx = testClient(); + wireMockRule.stubFor( + WireMock.get(urlEqualTo("/api/v1/app")) + .willReturn(WireMock.ok().withBody(res))); + + svx.getApplication().list(); + + wireMockRule.verify(1, getRequestedFor(urlEqualTo("/api/v1/app"))); + } + } diff --git a/javascript/src/mockttp.test.ts b/javascript/src/mockttp.test.ts index af6378eac..671b1caba 100644 --- a/javascript/src/mockttp.test.ts +++ b/javascript/src/mockttp.test.ts @@ -450,4 +450,19 @@ describe("mockttp tests", () => { expect(requests.length).toBe(1); expect(requests[0].headers["idempotency-key"]).toBe(clientProvidedKey); }); + + test("test unknown keys are ignored", async () => { + const endpointMock = await mockServer + .forGet("/api/v1/app") + .thenReply( + 200, + `{"data":[],"done":true,"iterator":null,"prevIterator":null,"extra-key":"ignored"}` + ); + const svx = new Svix("token", { serverUrl: mockServer.url }); + + await svx.application.list(); + + const requests = await endpointMock.getSeenRequests(); + expect(requests.length).toBe(1); + }); }); diff --git a/kotlin/lib/src/main/kotlin/SvixHttpClient.kt b/kotlin/lib/src/main/kotlin/SvixHttpClient.kt index 9a8d42e9b..63bc8eb8d 100644 --- a/kotlin/lib/src/main/kotlin/SvixHttpClient.kt +++ b/kotlin/lib/src/main/kotlin/SvixHttpClient.kt @@ -14,6 +14,7 @@ internal constructor( private val baseUrl: HttpUrl, private val defaultHeaders: Map, private val retrySchedule: List, + private val jsonDeserializer: Json = Json { ignoreUnknownKeys = true } ) { private val client: OkHttpClient = OkHttpClient() @@ -44,8 +45,8 @@ internal constructor( } if (headers?.get("idempotency-key") == null && method == "POST") { - val uuid = UUID.randomUUID().toString() - reqBuilder.addHeader("idempotency-key", "auto_" + uuid) + val uuid = UUID.randomUUID().toString() + reqBuilder.addHeader("idempotency-key", "auto_" + uuid) } reqBuilder.addHeader("svix-req-id", Random.nextULong().toString()) @@ -59,10 +60,10 @@ internal constructor( } val bodyString = res.body!!.string() if (res.code == 204) { - return Json.decodeFromString("true") + return jsonDeserializer.decodeFromString("true") } if (res.code in 200..299) { - return Json.decodeFromString(bodyString) + return jsonDeserializer.decodeFromString(bodyString) } throw ApiException("Non 200 status code ${res.code}", res.code, bodyString) } diff --git a/kotlin/lib/src/test/com/svix/kotlin/WiremockTests.kt b/kotlin/lib/src/test/com/svix/kotlin/WiremockTests.kt index b5b374f70..83272b095 100644 --- a/kotlin/lib/src/test/com/svix/kotlin/WiremockTests.kt +++ b/kotlin/lib/src/test/com/svix/kotlin/WiremockTests.kt @@ -481,4 +481,19 @@ class WiremockTests { .withHeader("idempotency-key", equalTo(clientProvidedKey)), ) } + + @Test + fun testUnknownKeysAreIgnored() { + val res = + """{"data": [],"iterator": "iterator","prevIterator": "-iterator","done": true,"extra-field": "ignored"}""" + val svx = testClient() + wireMockServer.stubFor(get(urlEqualTo("/api/v1/app")).willReturn(ok().withBody(res))) + + runBlocking { svx.application.list() } + + wireMockServer.verify( + 1, + getRequestedFor(urlEqualTo("/api/v1/app")), + ) + } } diff --git a/python/tests/test_httpretty.py b/python/tests/test_httpretty.py index 35ecc513d..d06b18fd4 100644 --- a/python/tests/test_httpretty.py +++ b/python/tests/test_httpretty.py @@ -85,3 +85,17 @@ def test_client_provided_idempotency_key_is_not_overridden(): request = httpretty.latest_requests()[0] assert "idempotency-key" in request.headers assert request.headers["idempotency-key"] == client_provided_key + + + +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_unknown_keys_are_ignored(): + svx = svix.Svix("token", svix.SvixOptions(server_url="http://test.example")) + httpretty.register_uri( + httpretty.GET, + "http://test.example/api/v1/app", + body='{"data":[],"done":true,"iterator":null,"prevIterator":null,"extra-key":"ignored"}' + ) + + + svx.application.list() diff --git a/ruby/spec/webmock_tests_spec.rb b/ruby/spec/webmock_tests_spec.rb index 9c72f1fd4..e03f80620 100644 --- a/ruby/spec/webmock_tests_spec.rb +++ b/ruby/spec/webmock_tests_spec.rb @@ -433,4 +433,18 @@ request = WebMock::RequestRegistry.instance.requested_signatures.hash.first[0] expect(request.headers["Idempotency-Key"]).to(eq(client_provided_key)) end + + it "test unknown keys are ignored" do + stub_request(:get, "#{host}/api/v1/app") + .to_return( + status: 200, + body: '{"data":[],"done":true,"iterator":null,"prevIterator":null,"extra-key":"ignored"}' + ) + + svx.application.list() + + expect(WebMock).to( + have_requested(:get, "#{host}/api/v1/app") + ) + end end diff --git a/rust/tests/it/wiremock_tests.rs b/rust/tests/it/wiremock_tests.rs index 23c3c9b91..68ba3b690 100644 --- a/rust/tests/it/wiremock_tests.rs +++ b/rust/tests/it/wiremock_tests.rs @@ -131,3 +131,29 @@ async fn test_client_provided_idempotency_key_is_not_overridden() { "client provided idempotency key should not be overridden" ); } + +#[tokio::test] +async fn test_unknown_keys_are_ignored() { + let mock_server = MockServer::start().await; + + let json_body = + r#"{"data":[],"done":true,"iterator":null,"prevIterator":null,"extra-key":"ignored"}"#; + Mock::given(method("GET")) + .and(path("/api/v1/app")) + .respond_with(ResponseTemplate::new(200).set_body_string(json_body)) + .expect(1) + .mount(&mock_server) + .await; + + let svx = Svix::new( + "token".to_string(), + Some(SvixOptions { + server_url: Some(mock_server.uri()), + ..Default::default() + }), + ); + + svx.application().list(None).await.unwrap(); + + mock_server.verify().await; +}