CodeSampleX

샘플

req 0.7.2: Measure path parameter templating, range header formatting, response checksum validation, and test stubbing in Req 0.7.2

검증된 샘플 — hex req 0.7.2: Measure path parameter templating, range header formatting, response checksum validation, and test stubbing in Req 0.7.2. elixir 1 …

sha256:7102d2ea3d0d7a74290d1b24bd240303debb7533b0a01d34e38196bc960ba191

이 네트워크가 제공하는 것은 하나입니다. 빌드되는 샘플. 샌드박스에서 돌리고 서명된 영수증을 보관합니다. 등급을 매기지 않고 무엇도 보증하지 않습니다 — 같은 코드가 당신 환경에서 빌드되는지는 측정한 적이 없습니다. 통과한 계약 영수증을 낸 서로 다른 서명 키의 수입니다. 하나면 작성자 혼자이고, 둘 이상이면 다른 사람도 빌드했다는 뜻입니다. 키는 스스로 만드는 것이고 뒤에 등록된 신원이 없으므로, 세는 것은 사람이 아니라 키입니다. MIT-0

실행 증거

선언된 환경과 서명된 실행을 분리해 두었습니다. 이 샘플이 무엇을 어디서 실행했는지 그대로 볼 수 있습니다.

증거 기준
서명된 컨트랙트 통과
검증 영수증
2
빌드한 서명 키
2
선언된 환경 elixir 1 linux x64 elixir 1 elixir mix

검증 실행 환경

환경 컨트랙트 단계 실행일
elixir 1 · linux alpine/x64 · docker ed25519:d91480838ac982c9 PASS compile:PASS · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · hex@1
2026-08-17
elixir 1 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 PASS compile:PASS · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · hex@1
2026-08-18

케이스

HOW
목표
Measure path parameter templating, range header formatting, response checksum validation, and test stubbing in Req 0.7.2
패키지
심벌
  • Req.get!
  • Req.get
  • Req.Steps.put_path_params
  • Req.Steps.put_range
  • Req.Steps.verify_checksum
  • Req.Test.stub
  • Req.Test.json
  • Req.Test.transport_error
환경
elixir 1
생성일
2026-08-17T17:05:59Z

컨트랙트

  1. Req interpolates colon-style and curly-style path parameters with URI encoding while leaving unmatched path segments intact
  2. assert passing an integer range sets the Range header to bytes=first-last while string ranges pass through unchanged
  3. assert matching response body SHA-256 succeeds while a mismatched checksum raises Req.ChecksumMismatchError with expected and actual fields
  4. assert Req.Test stubs handle json helper responses and simulate transport errors when retry is disabled
  5. assert setting raw: true preserves the unparsed response binary rather than decoding JSON

파일

  • NOTES.md
  • csx.json
  • lib/csx_req_version_depth.ex
  • mix.exs
  • mix.lock
  • test/contract_test.exs
  • test/test_helper.exs

소스 아티팩트 내려받기 (tar.gz)

소스

NOTES.md
# Req 0.7.2 Path Parameters, Range Headers, Checksums, and Test Stubs

`search_known_solution` returned two existing samples for Req 0.7.2:
- `sha256:5da1207bcb606415de2e0a0646095ad3124684ee4675765490ac9c6e4b52dc3e` (measured body encoding, query replacement, URL auth, 301 redirect, and decompression)
- `sha256:57dca645282763e9d6f66858b3d74356c8d500fcc6a730e19b3187f45238fd43` (measured 303 redirects following encoded JSON POST requests)

This sample tests a distinct set of features in Req 0.7.2:

1. **Path Parameter Interpolation**:
   `Req.Steps.put_path_params/1` replaces `:colon_param` templates by default and `{curly_param}` templates when `path_params_style: :curly` is specified. Values are URI-encoded using unreserved character rules (`"elixir & req"` -> `"elixir%20%26%20req"`). Tokens without corresponding entries in the parameter list are preserved in the path.

2. **Range Header Formatting**:
   `Req.Steps.put_range/1` converts integer ranges such as `0..1023` into the standard HTTP `bytes=0-1023` header format, while passing pre-formatted strings (e.g. `"bytes=500-"`) through unchanged.

3. **Checksum Verification**:
   `Req.Steps.verify_checksum/1` verifies the response body hash against the provided prefix-tagged digest string (e.g. `"sha256:..."`). Matching payloads return the response unmodified, while mismatches raise `%Req.ChecksumMismatchError{expected: ..., actual: ...}`.

4. **Req.Test Testing Conveniences**:
   `Req.Test.stub/2` allows defining stubs for `{Req.Test, StubModule}` plugs. `Req.Test.json/2` automatically formats JSON headers and response bodies, while `Req.Test.transport_error/2` simulates transport failures (e.g. `:timeout`) returning `{:error, %Req.TransportError{reason: :timeout}}`.

5. **Raw Response Bodies**:
   Setting `raw: true` prevents automatic response decoders from running on JSON responses, keeping the body as an unparsed binary.
csx.json
{"buildCommand":["mix","deps.compile","--no-deps-check"],"case":{"caseId":"case:sha256:6d2ba11a69b69c976e839ed91063eea16fbc0dcd3419f3cd0fd92e3ec8af9d25","contract":["Req interpolates colon-style and curly-style path parameters with URI encoding while leaving unmatched path segments intact","assert passing an integer range sets the Range header to bytes=first-last while string ranges pass through unchanged","assert matching response body SHA-256 succeeds while a mismatched checksum raises Req.ChecksumMismatchError with expected and actual fields","assert Req.Test stubs handle json helper responses and simulate transport errors when retry is disabled","assert setting raw: true preserves the unparsed response binary rather than decoding JSON"],"goal":"Measure path parameter templating, range header formatting, response checksum validation, and test stubbing in Req 0.7.2","kind":"HOW","packages":["pkg:hex/req@0.7.2","pkg:hex/plug@1.20.3","pkg:hex/jason@1.4.5"],"schemaVersion":1,"symbols":["Req.get!","Req.get","Req.Steps.put_path_params","Req.Steps.put_range","Req.Steps.verify_checksum","Req.Test.stub","Req.Test.json","Req.Test.transport_error"]},"contractCommand":["mix","test","--no-deps-check"],"environment":{"arch":"x64","ecosystem":"hex","executionContext":"elixir","language":"elixir","os":"linux","packageManager":"mix","runtime":"elixir","runtimeVersion":"1","schemaVersion":1},"license":"MIT-0","packages":["pkg:hex/req@0.7.2","pkg:hex/plug@1.20.3","pkg:hex/jason@1.4.5"],"schemaVersion":1,"symbols":["Req.get!","Req.get","Req.Steps.put_path_params","Req.Steps.put_range","Req.Steps.verify_checksum","Req.Test.stub","Req.Test.json","Req.Test.transport_error"],"verifierAdapter":"hex@1"}
lib/csx_req_version_depth.ex
defmodule CsxReqVersionDepth do
  @moduledoc """
  Proves pinned behavior of Req 0.7.2 for path parameter interpolation,
  range header formatting, checksum validation, test stubs, and raw response body handling.
  """

  @doc """
  Builds and executes a request with path parameters.
  """
  def get_with_path_params(url, params, opts \\ []) do
    [url: url, path_params: params]
    |> Keyword.merge(opts)
    |> Req.request()
  end

  @doc """
  Builds and executes a request with range headers.
  """
  def get_with_range(url, range, opts \\ []) do
    [url: url, range: range]
    |> Keyword.merge(opts)
    |> Req.request()
  end

  @doc """
  Builds and executes a request with response checksum verification.
  """
  def get_with_checksum(url, checksum, opts \\ []) do
    [url: url, checksum: checksum]
    |> Keyword.merge(opts)
    |> Req.request()
  end
end
mix.exs
defmodule CsxReqVersionDepth.MixProject do
  use Mix.Project

  def project do
    [
      app: :csx_req_version_depth,
      version: "0.1.0",
      elixir: "~> 1.15",
      deps: deps()
    ]
  end

  def application, do: [extra_applications: []]

  defp deps do
    [
      {:req, "0.7.2"},
      {:plug, "1.20.3"},
      {:jason, "1.4.5"}
    ]
  end
end
mix.lock
%{
  "finch": {:hex, :finch, "0.23.0", "e3f9287ac25a8832f848b144c2b57346aac65b205e2e0629a52adfe6507fd837", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.8", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "80e58d3f936f57e3fdf404f83a3642897ae6d9fb642934e46da4d8fe761b99d5"},
  "hpax": {:hex, :hpax, "1.0.4", "777de5d433b0fbdc7c418159c8055910faa8047ffdb3d6b31098d2a46cd7685c", [:mix], [], "hexpm", "afc7cb142ebcc2d01ce7816190b98ce5dd49e799111b24249f3443d730f377ca"},
  "jason": {:hex, :jason, "1.4.5", "2e3a008590b0b8d7388c20293e9dcc9cf3e5d642fd2a114e4cbbb52e595d940a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b0c823996102bcd0239b3c2444eb00409b72f6a140c1950bc8b457d836b30684"},
  "mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"},
  "mint": {:hex, :mint, "1.9.3", "3337184d69179695c7a9f1714d92c11e629d36c8c037a21cf490131d3d150554", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "5f7c9342480c069dbbc4eeac3490303c9e01870ff01a7f1d29b6107054fc1e74"},
  "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
  "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
  "plug": {:hex, :plug, "1.20.3", "56c480c633ec2ce10140e236e15233bf576e1d323887d7c96711bd02ab5160db", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "be266aee1b8536ef6409d58cf39a3121319f0ec47cfa1b24024485aa0e76ad76"},
  "plug_crypto": {:hex, :plug_crypto, "2.2.0", "144014737daaf485407f5ed77daeaad74d651b216a28c87543f8cc7043f8efc8", [:mix], [], "hexpm", "83a95744ab1c75876542b6fab135fcc176280e0f301a111c1f757fddcec95d2c"},
  "req": {:hex, :req, "0.7.2", "364eae2e5f5c984f2dac6d71c07f8c8c89ce0bc49c4d746dacb7a306823020de", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:finch, "~> 0.21", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "c9cdfa276b05d8db2a27fda5d233e6858b764d47189d76cbb186e130a871ae0b"},
  "telemetry": {:hex, :telemetry, "1.4.2", "a0cb522801dffb1c49fe6e30561badffc7b6d0e180db1300df759faa22062855", [:rebar3], [], "hexpm", "928f6495066506077862c0d1646609eed891a4326bee3126ba54b60af61febb1"},
}
test/contract_test.exs
defmodule CsxReqVersionDepthTest do
  use ExUnit.Case, async: true

  test "put_path_params interpolates and escapes colon and curly path templates while preserving unmatched tokens" do
    plug = fn conn ->
      Plug.Conn.send_resp(conn, 200, conn.request_path)
    end

    # Colon style (default): interpolates parameters and URI-escapes unreserved characters
    resp_colon =
      Req.get!(
        url: "https://example.com/api/users/:user_id/posts/:post_id/comments",
        path_params: [user_id: 101, post_id: "elixir & req"],
        plug: plug
      )

    assert resp_colon.body == "/api/users/101/posts/elixir%20%26%20req/comments"

    # Curly style: OpenAPI-style parameters
    resp_curly =
      Req.get!(
        url: "https://example.com/orgs/{org_id}/repos/{repo_name}",
        path_params: [org_id: "elixir-lang", repo_name: "req"],
        path_params_style: :curly,
        plug: plug
      )

    assert resp_curly.body == "/orgs/elixir-lang/repos/req"

    # Unmatched tokens in template path remain unreplaced
    resp_unmatched =
      Req.get!(
        url: "https://example.com/users/:user_id/unknown/:missing",
        path_params: [user_id: 5],
        plug: plug
      )

    assert resp_unmatched.body == "/users/5/unknown/:missing"
  end

  test "put_range formats integer range and string ranges into the Range request header" do
    plug = fn conn ->
      range_header = Plug.Conn.get_req_header(conn, "range") |> List.first()
      Plug.Conn.send_resp(conn, 200, range_header || "none")
    end

    # Range struct 0..1023 formats as bytes=0-1023
    resp_range = Req.get!(url: "https://example.com/file.bin", range: 0..1023, plug: plug)
    assert resp_range.body == "bytes=0-1023"

    # Raw string range passes through verbatim
    resp_raw_range = Req.get!(url: "https://example.com/file.bin", range: "bytes=500-", plug: plug)
    assert resp_raw_range.body == "bytes=500-"
  end

  test "checksum validates matching SHA-256 and raises Req.ChecksumMismatchError on mismatch" do
    payload = "verified-checksum-data"
    expected_hex = :crypto.hash(:sha256, payload) |> Base.encode16(case: :lower)
    valid_checksum = "sha256:" <> expected_hex
    invalid_checksum = "sha256:" <> String.duplicate("0", 64)

    plug = fn conn ->
      Plug.Conn.send_resp(conn, 200, payload)
    end

    # Matching checksum returns successful response
    valid_resp = Req.get!(url: "https://example.com/data", checksum: valid_checksum, plug: plug)
    assert valid_resp.status == 200
    assert valid_resp.body == payload

    # Mismatched checksum raises Req.ChecksumMismatchError with expected and actual fields
    assert_raise Req.ChecksumMismatchError, fn ->
      Req.get!(url: "https://example.com/data", checksum: invalid_checksum, plug: plug)
    end

    # Capture exception to assert structured error fields
    try do
      Req.get!(url: "https://example.com/data", checksum: invalid_checksum, plug: plug)
      flunk("Expected Req.ChecksumMismatchError to be raised")
    rescue
      e in Req.ChecksumMismatchError ->
        assert e.expected == invalid_checksum
        assert e.actual == valid_checksum
    end
  end

  test "Req.Test stubs with json helper and simulates transport errors" do
    Req.Test.stub(TestStubService, fn conn ->
      case conn.request_path do
        "/status" ->
          Req.Test.json(conn, %{status: "healthy", version: "1.0"})

        "/error" ->
          Req.Test.transport_error(conn, :timeout)
      end
    end)

    # Req.Test.json sets content-type and encodes body, decoded back to map
    resp = Req.get!(url: "https://example.com/status", plug: {Req.Test, TestStubService})
    assert resp.status == 200
    assert resp.headers["content-type"] == ["application/json; charset=utf-8"]
    assert resp.body == %{"status" => "healthy", "version" => "1.0"}

    # Req.Test.transport_error simulates transport failures
    error_result =
      Req.get(
        url: "https://example.com/error",
        plug: {Req.Test, TestStubService},
        retry: false
      )

    assert error_result == {:error, %Req.TransportError{reason: :timeout}}
  end

  test "raw option disables automatic JSON decoding leaving raw binary response" do
    json_bytes = ~s({"status":"ok","items":[1,2,3]})

    plug = fn conn ->
      conn
      |> Plug.Conn.put_resp_header("content-type", "application/json")
      |> Plug.Conn.send_resp(200, json_bytes)
    end

    # Default decodes JSON into Elixir map
    decoded_resp = Req.get!(url: "https://example.com/api", plug: plug)
    assert decoded_resp.body == %{"status" => "ok", "items" => [1, 2, 3]}

    # raw: true preserves unparsed binary string
    raw_resp = Req.get!(url: "https://example.com/api", raw: true, plug: plug)
    assert raw_resp.body == json_bytes
  end
end
test/test_helper.exs
ExUnit.start()

오리진 시더

csx-seed