Skip to content

Commit 1f1e201

Browse files
authored
Add metadata support and OpenAPI contract test (#34)
* Suppress reporter output during tests Set LIZARD_TEST_MODE in test_helper unless LIZARD_REPORT is true. Add nocov markers for branches SimpleCov can't reach due to LIZARD_TEST_MODE early return. * Add github metadata to reporters * Add OpenAPI contract test against published spec * Fix shadowed exception in contract test * Fail contract tests by default, allow SKIP_CONTRACT_TESTS override
1 parent 13d726d commit 1f1e201

8 files changed

Lines changed: 270 additions & 4 deletions

Gemfile.lock

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,14 @@ GEM
1616
diff-lcs (1.6.2)
1717
docile (1.4.1)
1818
drb (2.2.3)
19+
hana (1.3.7)
1920
hashdiff (1.2.1)
2021
json (2.19.2)
22+
json_schemer (2.5.0)
23+
bigdecimal
24+
hana (~> 1.3)
25+
regexp_parser (~> 2.0)
26+
simpleidn (~> 0.2)
2127
language_server-protocol (3.17.0.5)
2228
lint_roller (1.1.0)
2329
minitest (6.0.2)
@@ -75,6 +81,7 @@ GEM
7581
simplecov_json_formatter (~> 0.1)
7682
simplecov-html (0.13.2)
7783
simplecov_json_formatter (0.1.4)
84+
simpleidn (0.2.3)
7885
standard (1.54.0)
7986
language_server-protocol (~> 3.17.0.2)
8087
lint_roller (~> 1.0)
@@ -102,6 +109,7 @@ PLATFORMS
102109
x86_64-darwin-20
103110

104111
DEPENDENCIES
112+
json_schemer (~> 2.0)
105113
lizard!
106114
minitest (>= 5.0)
107115
mocha (~> 3.0)

lib/lizard/minitest_reporter.rb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,20 @@ def send_to_lizard
4747
js_specs: 0,
4848
runtime: total_time,
4949
coverage: extract_coverage,
50-
ran_at: Time.now.iso8601
50+
ran_at: Time.now.iso8601,
51+
metadata: github_metadata
5152
}
5253

5354
Client.new.send_test_run(data)
5455
end
5556

57+
def github_metadata
58+
meta = {}
59+
meta[:github_run_id] = ENV["GITHUB_RUN_ID"] if ENV["GITHUB_RUN_ID"]
60+
meta[:github_repository] = ENV["GITHUB_REPOSITORY"] if ENV["GITHUB_REPOSITORY"]
61+
meta
62+
end
63+
5664
def extract_coverage
5765
return SimpleCov.result.covered_percent if defined?(SimpleCov) && SimpleCov.result
5866
0.0

lib/lizard/rspec_formatter.rb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ def dump_summary(summary)
2020
js_specs: 0,
2121
runtime: summary.duration,
2222
coverage: extract_coverage,
23-
ran_at: Time.now.iso8601
23+
ran_at: Time.now.iso8601,
24+
metadata: github_metadata
2425
}
2526

2627
Client.new.send_test_run(data)
@@ -41,6 +42,13 @@ def should_report?
4142
ENV["LIZARD_API_KEY"] && ENV["LIZARD_URL"]
4243
end
4344

45+
def github_metadata
46+
meta = {}
47+
meta[:github_run_id] = ENV["GITHUB_RUN_ID"] if ENV["GITHUB_RUN_ID"]
48+
meta[:github_repository] = ENV["GITHUB_REPOSITORY"] if ENV["GITHUB_REPOSITORY"]
49+
meta
50+
end
51+
4452
def extract_coverage
4553
return SimpleCov.result.covered_percent if defined?(SimpleCov) && SimpleCov.result
4654
0.0

lizard.gemspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,6 @@ Gem::Specification.new do |spec|
3030
spec.add_development_dependency "rspec", "~> 3.0"
3131
spec.add_development_dependency "simplecov", "~> 0.22.0"
3232
spec.add_development_dependency "webmock", "~> 3.0"
33+
spec.add_development_dependency "json_schemer", "~> 2.0"
3334
spec.add_development_dependency "mocha", "~> 3.0"
3435
end

test/client_test.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ def test_initialize_with_lizard_module_fallback
2929
end
3030

3131
def test_send_test_run_returns_early_when_not_configured
32+
ENV.delete("LIZARD_API_KEY")
33+
ENV.delete("LIZARD_URL")
3234
client = Lizard::Client.new(api_key: nil, url: nil)
3335
result = client.send_test_run({test: "data"})
3436

test/contract_test.rb

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
require "test_helper"
2+
require "net/http"
3+
require "yaml"
4+
require "json_schemer"
5+
6+
class ContractTest < Minitest::Test
7+
SPEC_URL = "https://djbender.github.io/lizard/openapi.yaml"
8+
9+
def setup
10+
@spec = fetch_spec
11+
if @spec.nil?
12+
msg = "OpenAPI spec not reachable at #{SPEC_URL}"
13+
ENV["SKIP_CONTRACT_TESTS"] ? skip(msg) : raise(msg)
14+
end
15+
@schemas = @spec.dig("components", "schemas")
16+
end
17+
18+
def test_rspec_payload_matches_request_schema
19+
payload = build_rspec_payload
20+
schemer = schemer_for("CreateTestRunRequest")
21+
22+
errors = schemer.validate(payload).to_a
23+
assert_empty errors, "RSpec payload failed validation:\n#{format_errors(errors)}"
24+
end
25+
26+
def test_rspec_payload_with_metadata_matches_request_schema
27+
payload = build_rspec_payload(
28+
github_run_id: "23419710055",
29+
github_repository: "djbender/lizard-ruby"
30+
)
31+
schemer = schemer_for("CreateTestRunRequest")
32+
33+
errors = schemer.validate(payload).to_a
34+
assert_empty errors, "RSpec payload with metadata failed validation:\n#{format_errors(errors)}"
35+
end
36+
37+
def test_minitest_payload_matches_request_schema
38+
payload = build_minitest_payload
39+
schemer = schemer_for("CreateTestRunRequest")
40+
41+
errors = schemer.validate(payload).to_a
42+
assert_empty errors, "Minitest payload failed validation:\n#{format_errors(errors)}"
43+
end
44+
45+
def test_success_response_matches_schema
46+
response = {"status" => "success", "id" => 1}
47+
schemer = schemer_for("SuccessResponse")
48+
49+
errors = schemer.validate(response).to_a
50+
assert_empty errors, "Success response failed validation:\n#{format_errors(errors)}"
51+
end
52+
53+
def test_error_response_matches_schema
54+
response = {"error" => "Invalid API key"}
55+
schemer = schemer_for("ErrorResponse")
56+
57+
errors = schemer.validate(response).to_a
58+
assert_empty errors, "Error response failed validation:\n#{format_errors(errors)}"
59+
end
60+
61+
private
62+
63+
def fetch_spec
64+
uri = URI(SPEC_URL)
65+
response = Net::HTTP.get_response(uri)
66+
return nil unless response.is_a?(Net::HTTPSuccess)
67+
YAML.safe_load(response.body)
68+
rescue SocketError, Errno::ECONNREFUSED, Timeout::Error
69+
nil
70+
end
71+
72+
def build_rspec_payload(github_run_id: nil, github_repository: nil)
73+
metadata = {}
74+
metadata["github_run_id"] = github_run_id if github_run_id
75+
metadata["github_repository"] = github_repository if github_repository
76+
77+
{
78+
"test_run" => {
79+
"commit_sha" => "abc123def456",
80+
"branch" => "main",
81+
"ruby_specs" => 42,
82+
"js_specs" => 0,
83+
"runtime" => 12.345,
84+
"coverage" => 95.5,
85+
"ran_at" => Time.now.iso8601,
86+
"metadata" => metadata
87+
}
88+
}
89+
end
90+
91+
def build_minitest_payload(github_run_id: nil, github_repository: nil)
92+
metadata = {}
93+
metadata["github_run_id"] = github_run_id if github_run_id
94+
metadata["github_repository"] = github_repository if github_repository
95+
96+
{
97+
"test_run" => {
98+
"commit_sha" => "abc123def456",
99+
"branch" => "main",
100+
"ruby_specs" => 10,
101+
"js_specs" => 0,
102+
"runtime" => 3.21,
103+
"coverage" => 88.0,
104+
"ran_at" => Time.now.iso8601,
105+
"metadata" => metadata
106+
}
107+
}
108+
end
109+
110+
def schemer_for(schema_name)
111+
schema = @schemas.fetch(schema_name)
112+
resolved = resolve_refs(schema)
113+
JSONSchemer.schema(resolved)
114+
end
115+
116+
def resolve_refs(node)
117+
case node
118+
when Hash
119+
if node.key?("$ref")
120+
ref_name = node["$ref"].split("/").last
121+
resolve_refs(@schemas.fetch(ref_name))
122+
else
123+
node.transform_values { |v| resolve_refs(v) }
124+
end
125+
when Array
126+
node.map { |v| resolve_refs(v) }
127+
else
128+
node
129+
end
130+
end
131+
132+
def format_errors(errors)
133+
errors.map { |e| " #{e["data_pointer"]}: #{e["type"]}#{e["details"]}" }.join("\n")
134+
end
135+
end

test/minitest_reporter_test.rb

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
require "test_helper"
22

33
class MinitestReporterTest < Minitest::Test
4-
LIZARD_ENV_KEYS = %w[LIZARD_TEST_MODE LIZARD_API_KEY LIZARD_URL LIZARD_REPORT].freeze
4+
LIZARD_ENV_KEYS = %w[LIZARD_TEST_MODE LIZARD_API_KEY LIZARD_URL LIZARD_REPORT GITHUB_RUN_ID GITHUB_REPOSITORY].freeze
55

66
def setup
77
@original_env = LIZARD_ENV_KEYS.to_h { |k| [k, ENV[k]] }
@@ -50,6 +50,58 @@ def test_report_sends_to_lizard_when_configured
5050
end
5151
end
5252

53+
def test_report_includes_github_metadata_when_env_vars_present
54+
ENV.delete("LIZARD_TEST_MODE")
55+
ENV["LIZARD_API_KEY"] = "test_key"
56+
ENV["LIZARD_URL"] = "https://test.example.com"
57+
ENV["LIZARD_REPORT"] = "true"
58+
ENV["GITHUB_RUN_ID"] = "23419710055"
59+
ENV["GITHUB_REPOSITORY"] = "djbender/lizard-ruby"
60+
61+
expected_metadata = {github_run_id: "23419710055", github_repository: "djbender/lizard-ruby"}
62+
63+
client = mock
64+
client.expects(:send_test_run).with(has_entry(:metadata, expected_metadata))
65+
66+
Lizard::Client.stubs(:new).returns(client)
67+
68+
@reporter.stubs(:count).returns(5)
69+
@reporter.stubs(:total_time).returns(1.5)
70+
@reporter.stubs(:`).with("git rev-parse HEAD").returns("abc123")
71+
@reporter.stubs(:`).with("git branch --show-current").returns("main")
72+
73+
SimpleCov.stubs(:result).returns(mock(covered_percent: 85.0))
74+
75+
capture_io do
76+
@reporter.report
77+
end
78+
end
79+
80+
def test_report_sends_empty_metadata_without_github_env_vars
81+
ENV.delete("LIZARD_TEST_MODE")
82+
ENV.delete("GITHUB_RUN_ID")
83+
ENV.delete("GITHUB_REPOSITORY")
84+
ENV["LIZARD_API_KEY"] = "test_key"
85+
ENV["LIZARD_URL"] = "https://test.example.com"
86+
ENV["LIZARD_REPORT"] = "true"
87+
88+
client = mock
89+
client.expects(:send_test_run).with(has_entry(:metadata, {}))
90+
91+
Lizard::Client.stubs(:new).returns(client)
92+
93+
@reporter.stubs(:count).returns(5)
94+
@reporter.stubs(:total_time).returns(1.5)
95+
@reporter.stubs(:`).with("git rev-parse HEAD").returns("abc123")
96+
@reporter.stubs(:`).with("git branch --show-current").returns("main")
97+
98+
SimpleCov.stubs(:result).returns(mock(covered_percent: 85.0))
99+
100+
capture_io do
101+
@reporter.report
102+
end
103+
end
104+
53105
def test_report_handles_nil_simplecov_result
54106
ENV.delete("LIZARD_TEST_MODE")
55107
ENV["LIZARD_API_KEY"] = "test_key"

test/rspec_formatter_test.rb

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
require "test_helper"
22

33
class RSpecFormatterTest < Minitest::Test
4-
LIZARD_ENV_KEYS = %w[LIZARD_TEST_MODE LIZARD_API_KEY LIZARD_URL LIZARD_REPORT].freeze
4+
LIZARD_ENV_KEYS = %w[LIZARD_TEST_MODE LIZARD_API_KEY LIZARD_URL LIZARD_REPORT GITHUB_RUN_ID GITHUB_REPOSITORY].freeze
55

66
def setup
77
@original_env = LIZARD_ENV_KEYS.to_h { |k| [k, ENV[k]] }
@@ -50,6 +50,58 @@ def test_dump_summary_sends_to_lizard_when_configured
5050
@formatter.dump_summary(summary)
5151
end
5252

53+
def test_dump_summary_includes_github_metadata_when_env_vars_present
54+
ENV.delete("LIZARD_TEST_MODE")
55+
ENV["LIZARD_API_KEY"] = "test_key"
56+
ENV["LIZARD_URL"] = "https://test.example.com"
57+
ENV["LIZARD_REPORT"] = "true"
58+
ENV["GITHUB_RUN_ID"] = "23419710055"
59+
ENV["GITHUB_REPOSITORY"] = "djbender/lizard-ruby"
60+
61+
expected_metadata = {github_run_id: "23419710055", github_repository: "djbender/lizard-ruby"}
62+
63+
client = mock
64+
client.expects(:send_test_run).with(has_entry(:metadata, expected_metadata))
65+
66+
Lizard::Client.stubs(:new).returns(client)
67+
68+
@formatter.stubs(:`).with("git rev-parse HEAD").returns("abc123")
69+
@formatter.stubs(:`).with("git branch --show-current").returns("main")
70+
71+
summary = mock
72+
summary.stubs(:example_count).returns(10)
73+
summary.stubs(:duration).returns(2.5)
74+
75+
SimpleCov.stubs(:result).returns(mock(covered_percent: 90.0))
76+
77+
@formatter.dump_summary(summary)
78+
end
79+
80+
def test_dump_summary_sends_empty_metadata_without_github_env_vars
81+
ENV.delete("LIZARD_TEST_MODE")
82+
ENV.delete("GITHUB_RUN_ID")
83+
ENV.delete("GITHUB_REPOSITORY")
84+
ENV["LIZARD_API_KEY"] = "test_key"
85+
ENV["LIZARD_URL"] = "https://test.example.com"
86+
ENV["LIZARD_REPORT"] = "true"
87+
88+
client = mock
89+
client.expects(:send_test_run).with(has_entry(:metadata, {}))
90+
91+
Lizard::Client.stubs(:new).returns(client)
92+
93+
@formatter.stubs(:`).with("git rev-parse HEAD").returns("abc123")
94+
@formatter.stubs(:`).with("git branch --show-current").returns("main")
95+
96+
summary = mock
97+
summary.stubs(:example_count).returns(10)
98+
summary.stubs(:duration).returns(2.5)
99+
100+
SimpleCov.stubs(:result).returns(mock(covered_percent: 90.0))
101+
102+
@formatter.dump_summary(summary)
103+
end
104+
53105
def test_dump_summary_handles_nil_simplecov_result
54106
ENV.delete("LIZARD_TEST_MODE")
55107
ENV["LIZARD_API_KEY"] = "test_key"

0 commit comments

Comments
 (0)