Skip to content

Commit d37b550

Browse files
CopilotGrantBirki
andcommitted
Add comprehensive unit tests for Core::Builder with 25 test cases
Co-authored-by: GrantBirki <[email protected]>
1 parent a7e400e commit d37b550

File tree

2 files changed

+314
-1
lines changed

2 files changed

+314
-1
lines changed
Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
1+
# frozen_string_literal: true
2+
3+
require_relative "../../../spec_helper"
4+
require "fileutils"
5+
6+
describe Hooks::Core::Builder do
7+
let(:temp_dir) { "/tmp/hooks_builder_test" }
8+
9+
before do
10+
FileUtils.mkdir_p(temp_dir)
11+
end
12+
13+
after do
14+
FileUtils.rm_rf(temp_dir)
15+
end
16+
17+
describe "#initialize" do
18+
it "initializes with no parameters" do
19+
builder = described_class.new
20+
21+
expect(builder.instance_variable_get(:@log)).to be_nil
22+
expect(builder.instance_variable_get(:@config_input)).to be_nil
23+
end
24+
25+
it "initializes with config parameter" do
26+
config = { log_level: "debug" }
27+
builder = described_class.new(config: config)
28+
29+
expect(builder.instance_variable_get(:@config_input)).to eq(config)
30+
end
31+
32+
it "initializes with custom logger" do
33+
logger = double("Logger")
34+
builder = described_class.new(log: logger)
35+
36+
expect(builder.instance_variable_get(:@log)).to eq(logger)
37+
end
38+
39+
it "initializes with both config and logger" do
40+
config = { environment: "test" }
41+
logger = double("Logger")
42+
builder = described_class.new(config: config, log: logger)
43+
44+
expect(builder.instance_variable_get(:@config_input)).to eq(config)
45+
expect(builder.instance_variable_get(:@log)).to eq(logger)
46+
end
47+
end
48+
49+
describe "#build" do
50+
context "with minimal configuration" do
51+
let(:builder) { described_class.new }
52+
53+
before do
54+
# Mock dependencies to prevent actual file system operations
55+
allow(Hooks::Core::ConfigLoader).to receive(:load).and_return({
56+
log_level: "info",
57+
environment: "test",
58+
endpoints_dir: "/nonexistent"
59+
})
60+
allow(Hooks::Core::ConfigValidator).to receive(:validate_global_config).and_return({
61+
log_level: "info",
62+
environment: "test",
63+
endpoints_dir: "/nonexistent"
64+
})
65+
allow(Hooks::Core::ConfigLoader).to receive(:load_endpoints).and_return([])
66+
allow(Hooks::Core::ConfigValidator).to receive(:validate_endpoints).and_return([])
67+
allow(Hooks::App::API).to receive(:create).and_return("mock_api")
68+
end
69+
70+
it "builds and returns an API instance" do
71+
result = builder.build
72+
73+
expect(result).to eq("mock_api")
74+
end
75+
76+
it "calls ConfigLoader.load with the config input" do
77+
expect(Hooks::Core::ConfigLoader).to receive(:load).with(config_path: nil)
78+
79+
builder.build
80+
end
81+
82+
it "validates the global configuration" do
83+
config = { log_level: "info", environment: "test", endpoints_dir: "/nonexistent" }
84+
expect(Hooks::Core::ConfigValidator).to receive(:validate_global_config).with(config)
85+
86+
builder.build
87+
end
88+
89+
it "loads endpoints from the endpoints directory" do
90+
config = { log_level: "info", environment: "test", endpoints_dir: "/nonexistent" }
91+
expect(Hooks::Core::ConfigLoader).to receive(:load_endpoints).with("/nonexistent")
92+
93+
builder.build
94+
end
95+
96+
it "validates the loaded endpoints" do
97+
expect(Hooks::Core::ConfigValidator).to receive(:validate_endpoints).with([])
98+
99+
builder.build
100+
end
101+
102+
it "creates API with all required parameters" do
103+
expect(Hooks::App::API).to receive(:create) do |args|
104+
expect(args[:config]).to be_a(Hash)
105+
expect(args[:endpoints]).to eq([])
106+
expect(args[:log]).to respond_to(:info)
107+
expect(args[:signal_handler]).to be_a(Hooks::Core::SignalHandler)
108+
"mock_api"
109+
end
110+
111+
builder.build
112+
end
113+
end
114+
115+
context "with custom configuration" do
116+
let(:config) { { log_level: "debug", environment: "development" } }
117+
let(:builder) { described_class.new(config: config) }
118+
119+
before do
120+
allow(Hooks::Core::ConfigLoader).to receive(:load).and_return(config)
121+
allow(Hooks::Core::ConfigValidator).to receive(:validate_global_config).and_return(config)
122+
allow(Hooks::Core::ConfigLoader).to receive(:load_endpoints).and_return([])
123+
allow(Hooks::Core::ConfigValidator).to receive(:validate_endpoints).and_return([])
124+
allow(Hooks::App::API).to receive(:create).and_return("mock_api")
125+
end
126+
127+
it "passes the custom config to ConfigLoader" do
128+
expect(Hooks::Core::ConfigLoader).to receive(:load).with(config_path: config)
129+
130+
builder.build
131+
end
132+
end
133+
134+
context "with custom logger" do
135+
let(:custom_logger) { double("Logger", info: nil) }
136+
let(:builder) { described_class.new(log: custom_logger) }
137+
138+
before do
139+
allow(Hooks::Core::ConfigLoader).to receive(:load).and_return({ log_level: "info" })
140+
allow(Hooks::Core::ConfigValidator).to receive(:validate_global_config).and_return({ log_level: "info" })
141+
allow(Hooks::Core::ConfigLoader).to receive(:load_endpoints).and_return([])
142+
allow(Hooks::Core::ConfigValidator).to receive(:validate_endpoints).and_return([])
143+
allow(Hooks::App::API).to receive(:create).and_return("mock_api")
144+
end
145+
146+
it "uses the custom logger instead of creating one" do
147+
expect(Hooks::Core::LoggerFactory).not_to receive(:create)
148+
149+
builder.build
150+
end
151+
152+
it "passes the custom logger to API.create" do
153+
expect(Hooks::App::API).to receive(:create) do |args|
154+
expect(args[:log]).to eq(custom_logger)
155+
"mock_api"
156+
end
157+
158+
builder.build
159+
end
160+
end
161+
162+
context "with endpoints" do
163+
let(:endpoints) do
164+
[
165+
{ path: "/webhook/test1", handler: "Handler1" },
166+
{ path: "/webhook/test2", handler: "Handler2" }
167+
]
168+
end
169+
let(:builder) { described_class.new }
170+
171+
before do
172+
allow(Hooks::Core::ConfigLoader).to receive(:load).and_return({
173+
endpoints_dir: "/test/endpoints"
174+
})
175+
allow(Hooks::Core::ConfigValidator).to receive(:validate_global_config).and_return({
176+
endpoints_dir: "/test/endpoints"
177+
})
178+
allow(Hooks::Core::ConfigLoader).to receive(:load_endpoints).and_return(endpoints)
179+
allow(Hooks::Core::ConfigValidator).to receive(:validate_endpoints).and_return(endpoints)
180+
allow(Hooks::App::API).to receive(:create).and_return("mock_api")
181+
end
182+
183+
it "loads endpoints from the specified directory" do
184+
expect(Hooks::Core::ConfigLoader).to receive(:load_endpoints).with("/test/endpoints")
185+
186+
builder.build
187+
end
188+
189+
it "validates the loaded endpoints" do
190+
expect(Hooks::Core::ConfigValidator).to receive(:validate_endpoints).with(endpoints)
191+
192+
builder.build
193+
end
194+
195+
it "passes validated endpoints to API.create" do
196+
expect(Hooks::App::API).to receive(:create) do |args|
197+
expect(args[:endpoints]).to eq(endpoints)
198+
"mock_api"
199+
end
200+
201+
builder.build
202+
end
203+
end
204+
205+
context "with logging" do
206+
let(:builder) { described_class.new }
207+
let(:mock_logger) { double("Logger", info: nil) }
208+
209+
before do
210+
allow(Hooks::Core::ConfigLoader).to receive(:load).and_return({
211+
log_level: "debug",
212+
environment: "test"
213+
})
214+
allow(Hooks::Core::ConfigValidator).to receive(:validate_global_config).and_return({
215+
log_level: "debug",
216+
environment: "test"
217+
})
218+
allow(Hooks::Core::ConfigLoader).to receive(:load_endpoints).and_return([])
219+
allow(Hooks::Core::ConfigValidator).to receive(:validate_endpoints).and_return([])
220+
allow(Hooks::Core::LoggerFactory).to receive(:create).and_return(mock_logger)
221+
allow(Hooks::App::API).to receive(:create).and_return("mock_api")
222+
end
223+
224+
it "creates a logger with the configured log level" do
225+
expect(Hooks::Core::LoggerFactory).to receive(:create).with(
226+
log_level: "debug",
227+
custom_logger: nil
228+
)
229+
230+
builder.build
231+
end
232+
233+
it "logs startup information" do
234+
expect(mock_logger).to receive(:info).with("starting hooks server v#{Hooks::VERSION}")
235+
expect(mock_logger).to receive(:info).with("config: 0 endpoints loaded")
236+
expect(mock_logger).to receive(:info).with("environment: test")
237+
expect(mock_logger).to receive(:info).with("available endpoints: ")
238+
239+
builder.build
240+
end
241+
242+
it "logs endpoint information when endpoints are present" do
243+
endpoints = [
244+
{ path: "/webhook/test1", handler: "Handler1" },
245+
{ path: "/webhook/test2", handler: "Handler2" }
246+
]
247+
allow(Hooks::Core::ConfigLoader).to receive(:load_endpoints).and_return(endpoints)
248+
allow(Hooks::Core::ConfigValidator).to receive(:validate_endpoints).and_return(endpoints)
249+
250+
expect(mock_logger).to receive(:info).with("config: 2 endpoints loaded")
251+
expect(mock_logger).to receive(:info).with("available endpoints: /webhook/test1, /webhook/test2")
252+
253+
builder.build
254+
end
255+
end
256+
257+
context "error handling" do
258+
let(:builder) { described_class.new }
259+
260+
it "raises ConfigurationError when global config validation fails" do
261+
allow(Hooks::Core::ConfigLoader).to receive(:load).and_return({})
262+
allow(Hooks::Core::ConfigValidator).to receive(:validate_global_config)
263+
.and_raise(Hooks::Core::ConfigValidator::ValidationError, "Invalid config")
264+
265+
expect {
266+
builder.build
267+
}.to raise_error(Hooks::Core::ConfigurationError,
268+
"Configuration validation failed: Invalid config")
269+
end
270+
271+
it "raises ConfigurationError when endpoint validation fails" do
272+
allow(Hooks::Core::ConfigLoader).to receive(:load).and_return({ endpoints_dir: "/test" })
273+
allow(Hooks::Core::ConfigValidator).to receive(:validate_global_config).and_return({ endpoints_dir: "/test" })
274+
allow(Hooks::Core::ConfigLoader).to receive(:load_endpoints).and_return([{}])
275+
allow(Hooks::Core::ConfigValidator).to receive(:validate_endpoints)
276+
.and_raise(Hooks::Core::ConfigValidator::ValidationError, "Invalid endpoint")
277+
278+
expect {
279+
builder.build
280+
}.to raise_error(Hooks::Core::ConfigurationError,
281+
"Endpoint validation failed: Invalid endpoint")
282+
end
283+
end
284+
end
285+
286+
describe "#load_and_validate_config" do
287+
let(:builder) { described_class.new }
288+
289+
it "is a private method" do
290+
expect(described_class.private_instance_methods).to include(:load_and_validate_config)
291+
end
292+
end
293+
294+
describe "#load_endpoints" do
295+
let(:builder) { described_class.new }
296+
297+
it "is a private method" do
298+
expect(described_class.private_instance_methods).to include(:load_endpoints)
299+
end
300+
end
301+
302+
describe "ConfigurationError" do
303+
it "is a StandardError" do
304+
expect(Hooks::Core::ConfigurationError.new).to be_a(StandardError)
305+
end
306+
307+
it "can be raised with a custom message" do
308+
expect {
309+
raise Hooks::Core::ConfigurationError, "Custom error"
310+
}.to raise_error(Hooks::Core::ConfigurationError, "Custom error")
311+
end
312+
end
313+
end
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
# frozen_string_literal: true
22

3-
REQUIRED_COVERAGE_PERCENTAGE = 66
3+
REQUIRED_COVERAGE_PERCENTAGE = 70

0 commit comments

Comments
 (0)