discourse/spec/requests/api/shared/shared_examples.rb
Martin Brennan 123de2c5e5
DEV: Improve API schema validation debugging output (#38997)
Show the actual value at the failing JSON pointer path instead of
dumping the entire response body. Also shows the parent object
for additional context.

Before (entire response dumped regardless of where the error is):

RESPONSE: {"user"=>{"id"=>1, "username"=>"admin", ...hundreds of
keys...}}
    POSSIBLE ISSUE W/: /user/admin_incoming_sign_in_count

After (pinpoints the failing value and its parent):

    VALUE AT "/user/admin_incoming_sign_in_count": nil
    PARENT AT "/user": {"id"=>1, "username"=>"admin", ...}

Falls back to printing the full response only when no pointer is
available. Adds a `value_at_json_pointer` helper implementing
RFC 6901 path resolution.
2026-03-31 18:00:48 +10:00

75 lines
2.1 KiB
Ruby

# frozen_string_literal: true
RSpec.shared_examples "a JSON endpoint" do |expected_response_status|
before { |example| submit_request(example.metadata) }
# JSON Pointer (RFC 6901) path, e.g. "/upcoming_changes_stats/0/name" resolves to that value.
def value_at_json_pointer(data, pointer)
return data if pointer.blank?
pointer
.to_s
.split("/")
.reject(&:empty?)
.reduce(data) do |node, segment|
key = segment.match?(/\A\d+\z/) ? segment.to_i : segment
node&.dig(key)
end
end
def expect_schema_valid(schemer, params)
valid = schemer.valid?(params)
unless valid
validation_result = schemer.validate(params).to_a[0]
pointer = validation_result["data_pointer"]
if pointer.present?
at_pointer = value_at_json_pointer(params, pointer)
puts "VALUE AT #{pointer.inspect}: #{at_pointer.inspect}"
parent_pointer = pointer.sub(%r{/[^/]+\z}, "")
if parent_pointer != pointer && parent_pointer.present?
parent = value_at_json_pointer(params, parent_pointer)
puts "PARENT AT #{parent_pointer.inspect}: #{parent.inspect}"
end
else
puts "RESPONSE: #{params}"
end
details = validation_result["details"]
if details
puts "VALIDATION DETAILS: #{details}"
else
puts "POSSIBLE ISSUE W/: #{pointer}" if pointer.present?
end
end
expect(valid).to eq(true)
end
describe "response status" do
it "returns expected response status" do
expect(response.status).to eq(expected_response_status)
end
end
describe "request body" do
it "matches the documented request schema" do |example|
if expected_request_schema
schemer = JSONSchemer.schema(expected_request_schema)
expect_schema_valid(schemer, params)
end
end
end
describe "response body" do
let(:json_response) { JSON.parse(response.body) }
it "matches the documented response schema" do |example|
if expected_response_schema
schemer = JSONSchemer.schema(expected_response_schema)
expect_schema_valid(schemer, json_response)
end
end
end
end