Skip to content

Commit 639f758

Browse files
CopilotGrantBirki
andcommitted
Add comprehensive tests for app helpers, auth, handlers, and endpoints
Co-authored-by: GrantBirki <[email protected]>
1 parent c064a89 commit 639f758

File tree

6 files changed

+770
-0
lines changed

6 files changed

+770
-0
lines changed
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
# frozen_string_literal: true
2+
3+
describe Hooks::App::Auth do
4+
# Create a test class that includes the auth module
5+
let(:test_class) do
6+
Class.new do
7+
include Hooks::App::Auth
8+
9+
def error!(message, code)
10+
raise StandardError, "#{message} (#{code})"
11+
end
12+
end
13+
end
14+
15+
let(:auth_instance) { test_class.new }
16+
let(:payload) { "test payload" }
17+
let(:headers) { { "Content-Type" => "application/json" } }
18+
19+
describe "#validate_auth!" do
20+
context "when auth config has secret_env_key" do
21+
let(:endpoint_config) do
22+
{
23+
auth: {
24+
type: "hmac",
25+
secret_env_key: "TEST_SECRET"
26+
}
27+
}
28+
end
29+
30+
context "when secret exists in environment" do
31+
before do
32+
ENV["TEST_SECRET"] = "test-secret-value"
33+
end
34+
35+
after do
36+
ENV.delete("TEST_SECRET")
37+
end
38+
39+
context "with HMAC auth type" do
40+
it "validates with HMAC plugin when authentication succeeds" do
41+
allow(Hooks::Plugins::Auth::HMAC).to receive(:valid?).and_return(true)
42+
43+
expect { auth_instance.validate_auth!(payload, headers, endpoint_config) }
44+
.not_to raise_error
45+
46+
expect(Hooks::Plugins::Auth::HMAC).to have_received(:valid?).with(
47+
payload: payload,
48+
headers: headers,
49+
secret: "test-secret-value",
50+
config: endpoint_config
51+
)
52+
end
53+
54+
it "raises authentication failed error when HMAC validation fails" do
55+
allow(Hooks::Plugins::Auth::HMAC).to receive(:valid?).and_return(false)
56+
57+
expect { auth_instance.validate_auth!(payload, headers, endpoint_config) }
58+
.to raise_error(StandardError, "authentication failed (401)")
59+
end
60+
end
61+
62+
context "with shared_secret auth type" do
63+
let(:endpoint_config) do
64+
{
65+
auth: {
66+
type: "shared_secret",
67+
secret_env_key: "TEST_SECRET"
68+
}
69+
}
70+
end
71+
72+
it "validates with SharedSecret plugin when authentication succeeds" do
73+
allow(Hooks::Plugins::Auth::SharedSecret).to receive(:valid?).and_return(true)
74+
75+
expect { auth_instance.validate_auth!(payload, headers, endpoint_config) }
76+
.not_to raise_error
77+
78+
expect(Hooks::Plugins::Auth::SharedSecret).to have_received(:valid?).with(
79+
payload: payload,
80+
headers: headers,
81+
secret: "test-secret-value",
82+
config: endpoint_config
83+
)
84+
end
85+
86+
it "raises authentication failed error when SharedSecret validation fails" do
87+
allow(Hooks::Plugins::Auth::SharedSecret).to receive(:valid?).and_return(false)
88+
89+
expect { auth_instance.validate_auth!(payload, headers, endpoint_config) }
90+
.to raise_error(StandardError, "authentication failed (401)")
91+
end
92+
end
93+
94+
context "with unsupported auth type" do
95+
let(:endpoint_config) do
96+
{
97+
auth: {
98+
type: "custom",
99+
secret_env_key: "TEST_SECRET"
100+
}
101+
}
102+
end
103+
104+
it "raises custom validators not implemented error" do
105+
expect { auth_instance.validate_auth!(payload, headers, endpoint_config) }
106+
.to raise_error(StandardError, "Custom validators not implemented in POC (500)")
107+
end
108+
end
109+
110+
context "with case variations in auth type" do
111+
let(:endpoint_config) do
112+
{
113+
auth: {
114+
type: "HMAC",
115+
secret_env_key: "TEST_SECRET"
116+
}
117+
}
118+
end
119+
120+
it "handles uppercase auth type" do
121+
allow(Hooks::Plugins::Auth::HMAC).to receive(:valid?).and_return(true)
122+
123+
expect { auth_instance.validate_auth!(payload, headers, endpoint_config) }
124+
.not_to raise_error
125+
end
126+
end
127+
end
128+
129+
context "when secret does not exist in environment" do
130+
let(:endpoint_config) do
131+
{
132+
auth: {
133+
type: "hmac",
134+
secret_env_key: "NONEXISTENT_SECRET"
135+
}
136+
}
137+
end
138+
139+
it "raises secret not found error" do
140+
ENV.delete("NONEXISTENT_SECRET") # Ensure it's not set
141+
142+
expect { auth_instance.validate_auth!(payload, headers, endpoint_config) }
143+
.to raise_error(StandardError, "secret 'NONEXISTENT_SECRET' not found in environment (500)")
144+
end
145+
end
146+
end
147+
148+
context "when auth config has no secret_env_key" do
149+
let(:endpoint_config) do
150+
{
151+
auth: {
152+
type: "hmac"
153+
}
154+
}
155+
end
156+
157+
it "returns without validation" do
158+
expect { auth_instance.validate_auth!(payload, headers, endpoint_config) }
159+
.not_to raise_error
160+
161+
# No auth plugins should be called
162+
expect(Hooks::Plugins::Auth::HMAC).not_to receive(:valid?)
163+
expect(Hooks::Plugins::Auth::SharedSecret).not_to receive(:valid?)
164+
end
165+
end
166+
167+
context "when auth config has nil secret_env_key" do
168+
let(:endpoint_config) do
169+
{
170+
auth: {
171+
type: "hmac",
172+
secret_env_key: nil
173+
}
174+
}
175+
end
176+
177+
it "returns without validation" do
178+
expect { auth_instance.validate_auth!(payload, headers, endpoint_config) }
179+
.not_to raise_error
180+
end
181+
end
182+
183+
context "when auth config has empty secret_env_key" do
184+
let(:endpoint_config) do
185+
{
186+
auth: {
187+
type: "hmac",
188+
secret_env_key: ""
189+
}
190+
}
191+
end
192+
193+
it "raises secret not found error for empty string" do
194+
ENV.delete("") # Ensure empty string key is not set
195+
196+
expect { auth_instance.validate_auth!(payload, headers, endpoint_config) }
197+
.to raise_error(StandardError, "secret '' not found in environment (500)")
198+
end
199+
end
200+
end
201+
end
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# frozen_string_literal: true
2+
3+
describe Hooks::App::HealthEndpoint do
4+
# Test the endpoint behavior using a mock API instance
5+
let(:api_instance) do
6+
Class.new(Grape::API) do
7+
mount Hooks::App::HealthEndpoint
8+
end
9+
end
10+
11+
before do
12+
# Mock the API start time
13+
allow(Hooks::App::API).to receive(:start_time).and_return(Time.parse("2025-01-01T00:00:00Z"))
14+
end
15+
16+
describe "GET /" do
17+
let(:response) { api_instance.new.call(Rack::MockRequest.env_for("/")) }
18+
19+
it "returns 200 status" do
20+
expect(response[0]).to eq(200)
21+
end
22+
23+
it "returns JSON content type" do
24+
headers = response[1]
25+
expect(headers["Content-Type"]).to include("application/json")
26+
end
27+
28+
it "returns health status information" do
29+
body = JSON.parse(response[2].first)
30+
31+
expect(body["status"]).to eq("healthy")
32+
expect(body["timestamp"]).to eq(TIME_MOCK)
33+
expect(body["version"]).to eq(Hooks::VERSION)
34+
expect(body["uptime_seconds"]).to be_a(Integer)
35+
expect(body["uptime_seconds"]).to eq(0) # Since mocked time is the same as start time
36+
end
37+
38+
it "calculates uptime correctly" do
39+
# Set different start time to test uptime calculation
40+
start_time = Time.parse("2024-12-31T23:59:30Z")
41+
allow(Hooks::App::API).to receive(:start_time).and_return(start_time)
42+
43+
body = JSON.parse(response[2].first)
44+
expect(body["uptime_seconds"]).to eq(30) # 30 seconds difference
45+
end
46+
47+
it "includes all required fields" do
48+
body = JSON.parse(response[2].first)
49+
50+
expect(body).to have_key("status")
51+
expect(body).to have_key("timestamp")
52+
expect(body).to have_key("version")
53+
expect(body).to have_key("uptime_seconds")
54+
end
55+
56+
it "returns valid JSON" do
57+
expect { JSON.parse(response[2].first) }.not_to raise_error
58+
end
59+
end
60+
61+
describe "inheritance" do
62+
it "inherits from Grape::API" do
63+
expect(described_class.superclass).to eq(Grape::API)
64+
end
65+
end
66+
end
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# frozen_string_literal: true
2+
3+
describe Hooks::App::VersionEndpoint do
4+
# Test the endpoint behavior using a mock API instance
5+
let(:api_instance) do
6+
Class.new(Grape::API) do
7+
mount Hooks::App::VersionEndpoint
8+
end
9+
end
10+
11+
describe "GET /" do
12+
let(:response) { api_instance.new.call(Rack::MockRequest.env_for("/")) }
13+
14+
it "returns 200 status" do
15+
expect(response[0]).to eq(200)
16+
end
17+
18+
it "returns JSON content type" do
19+
headers = response[1]
20+
expect(headers["Content-Type"]).to include("application/json")
21+
end
22+
23+
it "returns version information" do
24+
body = JSON.parse(response[2].first)
25+
26+
expect(body["version"]).to eq(Hooks::VERSION)
27+
expect(body["timestamp"]).to eq(TIME_MOCK)
28+
end
29+
30+
it "includes all required fields" do
31+
body = JSON.parse(response[2].first)
32+
33+
expect(body).to have_key("version")
34+
expect(body).to have_key("timestamp")
35+
end
36+
37+
it "has exactly two fields" do
38+
body = JSON.parse(response[2].first)
39+
expect(body.keys.length).to eq(2)
40+
end
41+
42+
it "returns valid JSON" do
43+
expect { JSON.parse(response[2].first) }.not_to raise_error
44+
end
45+
46+
it "returns current version from Hooks::VERSION" do
47+
body = JSON.parse(response[2].first)
48+
expect(body["version"]).to match(/^\d+\.\d+\.\d+$/)
49+
end
50+
51+
it "returns timestamp in ISO 8601 format" do
52+
body = JSON.parse(response[2].first)
53+
expect(body["timestamp"]).to match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/)
54+
end
55+
end
56+
57+
describe "inheritance" do
58+
it "inherits from Grape::API" do
59+
expect(described_class.superclass).to eq(Grape::API)
60+
end
61+
end
62+
end

0 commit comments

Comments
 (0)