From 35c8cf9b3f9db4e052e5ce53e9e713a0e633aafe Mon Sep 17 00:00:00 2001 From: Matt Sutkowski Date: Sat, 16 Dec 2023 09:45:42 -0800 Subject: [PATCH 1/4] fix: assert_operation_response header lookup --- lib/open_api_spex/cast/utils.ex | 16 +++++++++++++--- lib/open_api_spex/test/test_assertions.ex | 2 +- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/open_api_spex/cast/utils.ex b/lib/open_api_spex/cast/utils.ex index 5be0b02a..c2ba810b 100644 --- a/lib/open_api_spex/cast/utils.ex +++ b/lib/open_api_spex/cast/utils.ex @@ -73,9 +73,19 @@ defmodule OpenApiSpex.Cast.Utils do - If multiple `content-type` headers are found, the function will only return the value of the first one. """ - @spec content_type_from_header(Plug.Conn.t()) :: String.t() | nil - def content_type_from_header(conn = %Plug.Conn{}) do - case Plug.Conn.get_req_header(conn, "content-type") do + @spec content_type_from_header(Plug.Conn.t(), :request | :response) :: + String.t() | nil + def content_type_from_header(conn = %Plug.Conn{}, header_location \\ :request) do + content_type = + case header_location do + :request -> + Plug.Conn.get_req_header(conn, "content-type") + + :response -> + Plug.Conn.get_resp_header(conn, "content-type") + end + + case content_type do [header_value | _] -> header_value |> String.split(";") diff --git a/lib/open_api_spex/test/test_assertions.ex b/lib/open_api_spex/test/test_assertions.ex index 42f95b2e..61253135 100644 --- a/lib/open_api_spex/test/test_assertions.ex +++ b/lib/open_api_spex/test/test_assertions.ex @@ -154,7 +154,7 @@ defmodule OpenApiSpex.TestAssertions do ) :: term | no_return defp validate_operation_response(conn, %Operation{operationId: operation_id} = operation, spec) do - content_type = Utils.content_type_from_header(conn) + content_type = Utils.content_type_from_header(conn, :response) resolved_schema = get_in(operation, [ From 9deee8501f976206d9ef41b3f70e835ec5ebc27e Mon Sep 17 00:00:00 2001 From: Matt Sutkowski Date: Sat, 16 Dec 2023 10:47:29 -0800 Subject: [PATCH 2/4] update copy and add test for resp content type --- lib/open_api_spex/test/test_assertions.ex | 2 +- test/test_assertions_test.exs | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/open_api_spex/test/test_assertions.ex b/lib/open_api_spex/test/test_assertions.ex index 61253135..65d748a8 100644 --- a/lib/open_api_spex/test/test_assertions.ex +++ b/lib/open_api_spex/test/test_assertions.ex @@ -167,7 +167,7 @@ defmodule OpenApiSpex.TestAssertions do if is_nil(resolved_schema) do flunk( - "Failed to resolve schema! Unable to find a response for operation_id: #{operation_id} for response status code: #{conn.status} and content type #{content_type}" + "Failed to resolve a response schema for operation_id: #{operation_id} for status code: #{conn.status} and content type: #{content_type}" ) end diff --git a/test/test_assertions_test.exs b/test/test_assertions_test.exs index 0959eef5..1b89cddc 100644 --- a/test/test_assertions_test.exs +++ b/test/test_assertions_test.exs @@ -158,5 +158,26 @@ defmodule OpenApiSpex.TestAssertionsTest do "Value does not conform to schema PetResponse: Failed to cast value to one of: no schemas validate at" end end + + test "returns an error when the response content-type does not match the schema" do + conn = + :get + |> Plug.Test.conn("/api/pets") + |> Plug.Conn.put_req_header("content-type", "application/json") + |> Plug.Conn.put_resp_header("content-type", "unexpected-content-type") + + conn = OpenApiSpexTest.Router.call(conn, []) + + assert conn.status == 200 + + try do + TestAssertions.assert_operation_response(conn, "showPetById") + raise RuntimeError, "Should flunk" + rescue + e in ExUnit.AssertionError -> + assert e.message =~ + "Failed to resolve a response schema for operation_id: showPetById for status code: 200 and content type: unexpected-content-type" + end + end end end From 81a7fc5e20e1efa1568b07a6816202309c5edc66 Mon Sep 17 00:00:00 2001 From: Matt Sutkowski Date: Sat, 16 Dec 2023 11:13:47 -0800 Subject: [PATCH 3/4] use Map.get --- lib/open_api_spex/test/test_assertions.ex | 15 +++++++-------- test/test_assertions_test.exs | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/open_api_spex/test/test_assertions.ex b/lib/open_api_spex/test/test_assertions.ex index 65d748a8..6d089024 100644 --- a/lib/open_api_spex/test/test_assertions.ex +++ b/lib/open_api_spex/test/test_assertions.ex @@ -136,7 +136,7 @@ defmodule OpenApiSpex.TestAssertions do case operation_lookup[operation_id] do nil -> flunk( - "Failed to resolve schema. Unable to find a response for operation_id: #{operation_id} for response status code: #{conn.status}" + "Failed to resolve a response schema for operation_id: #{operation_id} for status code: #{conn.status}" ) operation -> @@ -157,13 +157,12 @@ defmodule OpenApiSpex.TestAssertions do content_type = Utils.content_type_from_header(conn, :response) resolved_schema = - get_in(operation, [ - Access.key!(:responses), - Access.key!(conn.status), - Access.key!(:content), - content_type, - Access.key!(:schema) - ]) + operation + |> Map.get(:responses, %{}) + |> Map.get(conn.status, %{}) + |> Map.get(:content, %{}) + |> Map.get(content_type, %{}) + |> Map.get(:schema) if is_nil(resolved_schema) do flunk( diff --git a/test/test_assertions_test.exs b/test/test_assertions_test.exs index 1b89cddc..a416feba 100644 --- a/test/test_assertions_test.exs +++ b/test/test_assertions_test.exs @@ -135,7 +135,7 @@ defmodule OpenApiSpex.TestAssertionsTest do rescue e in ExUnit.AssertionError -> assert e.message =~ - "Failed to resolve schema. Unable to find a response for operation_id: not_a_real_operation_id for response status code: 200" + "Failed to resolve a response schema for operation_id: not_a_real_operation_id for status code: 200" end end From 3dc3fad0b6899e78501ae4229c7bba0205075c7a Mon Sep 17 00:00:00 2001 From: Matt Sutkowski Date: Mon, 18 Dec 2023 08:19:30 -0800 Subject: [PATCH 4/4] switch to assert_raise --- test/test_assertions_test.exs | 39 ++++++++++++++--------------------- 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/test/test_assertions_test.exs b/test/test_assertions_test.exs index a416feba..9174b5e2 100644 --- a/test/test_assertions_test.exs +++ b/test/test_assertions_test.exs @@ -129,14 +129,11 @@ defmodule OpenApiSpex.TestAssertionsTest do conn = OpenApiSpexTest.Router.call(conn, []) assert conn.status == 200 - try do - TestAssertions.assert_operation_response(conn, "not_a_real_operation_id") - raise RuntimeError, "Should flunk" - rescue - e in ExUnit.AssertionError -> - assert e.message =~ - "Failed to resolve a response schema for operation_id: not_a_real_operation_id for status code: 200" - end + assert_raise( + ExUnit.AssertionError, + ~r/Failed to resolve a response schema for operation_id: not_a_real_operation_id for status code: 200/, + fn -> TestAssertions.assert_operation_response(conn, "not_a_real_operation_id") end + ) end test "invalid schema" do @@ -149,14 +146,11 @@ defmodule OpenApiSpex.TestAssertionsTest do assert conn.status == 200 - try do - TestAssertions.assert_operation_response(conn, "showPetById") - raise RuntimeError, "Should flunk" - rescue - e in ExUnit.AssertionError -> - assert e.message =~ - "Value does not conform to schema PetResponse: Failed to cast value to one of: no schemas validate at" - end + assert_raise( + ExUnit.AssertionError, + ~r/Value does not conform to schema PetResponse: Failed to cast value to one of: no schemas validate at/, + fn -> TestAssertions.assert_operation_response(conn, "showPetById") end + ) end test "returns an error when the response content-type does not match the schema" do @@ -170,14 +164,11 @@ defmodule OpenApiSpex.TestAssertionsTest do assert conn.status == 200 - try do - TestAssertions.assert_operation_response(conn, "showPetById") - raise RuntimeError, "Should flunk" - rescue - e in ExUnit.AssertionError -> - assert e.message =~ - "Failed to resolve a response schema for operation_id: showPetById for status code: 200 and content type: unexpected-content-type" - end + assert_raise( + ExUnit.AssertionError, + ~r/Failed to resolve a response schema for operation_id: showPetById for status code: 200 and content type: unexpected-content-type/, + fn -> TestAssertions.assert_operation_response(conn, "showPetById") end + ) end end end