Skip to content

Commit 2558857

Browse files
CopilotGrantBirki
andcommitted
Update ConfigValidator and PluginLoader for snake_case handlers
Co-authored-by: GrantBirki <[email protected]>
1 parent b08dc3b commit 2558857

12 files changed

+81
-74
lines changed

lib/hooks/app/helpers.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def parse_payload(raw_body, headers, symbolize: false)
7474

7575
# Load handler class
7676
#
77-
# @param handler_class_name [String] The name of the handler class to load
77+
# @param handler_class_name [String] The name of the handler in snake_case (e.g., "github_handler")
7878
# @return [Object] An instance of the loaded handler class
7979
# @raise [StandardError] If handler cannot be found
8080
def load_handler(handler_class_name)

lib/hooks/core/config_validator.rb

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -125,11 +125,12 @@ def self.valid_handler_name?(handler_name)
125125
# Must not be empty or only whitespace
126126
return false if handler_name.strip.empty?
127127

128-
# Must match a safe pattern: alphanumeric + underscore, starting with uppercase
129-
return false unless handler_name.match?(/\A[A-Z][a-zA-Z0-9_]*\z/)
128+
# Must match a safe pattern: alphanumeric + underscore, starting with lowercase
129+
return false unless handler_name.match?(/\A[a-z][a-z0-9_]*\z/)
130130

131-
# Must not be a system/built-in class name
132-
return false if Hooks::Security::DANGEROUS_CLASSES.include?(handler_name)
131+
# Convert to PascalCase for security check (since DANGEROUS_CLASSES uses PascalCase)
132+
pascal_case_name = handler_name.split("_").map(&:capitalize).join("")
133+
return false if Hooks::Security::DANGEROUS_CLASSES.include?(pascal_case_name)
133134

134135
true
135136
end

lib/hooks/core/plugin_loader.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,13 @@ def get_auth_plugin(plugin_name)
6161

6262
# Get handler plugin class by name
6363
#
64-
# @param handler_name [String] Name of the handler (e.g., "DefaultHandler", "Team1Handler")
64+
# @param handler_name [String] Name of the handler in snake_case (e.g., "github_handler", "team_1_handler")
6565
# @return [Class] The handler plugin class
6666
# @raise [StandardError] if handler not found
6767
def get_handler_plugin(handler_name)
68-
plugin_class = @handler_plugins[handler_name]
68+
# Convert snake_case to PascalCase for registry lookup
69+
pascal_case_name = handler_name.split("_").map(&:capitalize).join("")
70+
plugin_class = @handler_plugins[pascal_case_name]
6971

7072
unless plugin_class
7173
raise StandardError, "Handler plugin '#{handler_name}' not found. Available handlers: #{@handler_plugins.keys.join(', ')}"

spec/integration/global_lifecycle_hooks_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ def call(payload:, headers:, env:, config:)
103103
# Create an endpoint configuration
104104
endpoint_config_content = <<~YAML
105105
path: /integration-test
106-
handler: IntegrationTestHandler
106+
handler: integration_test_handler
107107
YAML
108108
File.write(File.join(temp_endpoints_dir, "integration_test.yml"), endpoint_config_content)
109109
end

spec/integration/hooks_integration_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def app
3333
FileUtils.mkdir_p("./spec/integration/tmp/endpoints")
3434
File.write("./spec/integration/tmp/endpoints/test.yaml", {
3535
path: "/test",
36-
handler: "TestHandler",
36+
handler: "test_handler",
3737
opts: { test_mode: true }
3838
}.to_yaml)
3939

spec/unit/app/rack_env_builder_spec.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
let(:request_context) do
1717
{
1818
request_id: "req-123",
19-
handler: "TestHandler"
19+
handler: "test_handler"
2020
}
2121
end
2222
let(:endpoint_config) do
@@ -86,7 +86,7 @@
8686

8787
it "includes Hooks-specific environment variables" do
8888
expect(result["hooks.request_id"]).to eq("req-123")
89-
expect(result["hooks.handler"]).to eq("TestHandler")
89+
expect(result["hooks.handler"]).to eq("test_handler")
9090
expect(result["hooks.endpoint_config"]).to eq(endpoint_config)
9191
expect(result["hooks.start_time"]).to eq("2025-06-16T10:30:45Z")
9292
expect(result["hooks.full_path"]).to eq("/api/v1/test")
@@ -280,7 +280,7 @@
280280
described_class.new(
281281
mock_request,
282282
{ "Accept" => "application/vnd.api+json" },
283-
{ request_id: "mock-123", handler: "MockHandler" },
283+
{ request_id: "mock-123", handler: "mock_handler" },
284284
{ path: "/resource", method: "patch" },
285285
Time.parse("2025-06-16T15:45:30Z"),
286286
"/api/v2/resource"

spec/unit/lib/hooks/app/helpers_security_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def env
5959
end
6060

6161
it "successfully loads DefaultHandler" do
62-
handler = instance.load_handler("DefaultHandler")
62+
handler = instance.load_handler("default_handler")
6363
expect(handler).to be_an_instance_of(DefaultHandler)
6464
end
6565
end

spec/unit/lib/hooks/app/helpers_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ def error!(message, code)
307307
end
308308

309309
it "successfully loads DefaultHandler" do
310-
handler = helper.load_handler("DefaultHandler")
310+
handler = helper.load_handler("default_handler")
311311
expect(handler).to be_an_instance_of(DefaultHandler)
312312
end
313313
end

spec/unit/lib/hooks/core/config_loader_spec.rb

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -281,14 +281,14 @@
281281
let(:endpoint1_config) do
282282
{
283283
"path" => "/webhook/test1",
284-
"handler" => "TestHandler1",
284+
"handler" => "test_handler_1",
285285
"method" => "POST"
286286
}
287287
end
288288
let(:endpoint2_config) do
289289
{
290290
"path" => "/webhook/test2",
291-
"handler" => "TestHandler2",
291+
"handler" => "test_handler_2",
292292
"method" => "PUT"
293293
}
294294
end
@@ -304,12 +304,12 @@
304304
expect(endpoints).to have_attributes(size: 2)
305305
expect(endpoints).to include(
306306
path: "/webhook/test1",
307-
handler: "TestHandler1",
307+
handler: "test_handler_1",
308308
method: "POST"
309309
)
310310
expect(endpoints).to include(
311311
path: "/webhook/test2",
312-
handler: "TestHandler2",
312+
handler: "test_handler_2",
313313
method: "PUT"
314314
)
315315
end
@@ -320,7 +320,7 @@
320320
let(:endpoint_config) do
321321
{
322322
"path" => "/webhook/json",
323-
"handler" => "JsonHandler",
323+
"handler" => "json_handler",
324324
"method" => "POST"
325325
}
326326
end
@@ -335,7 +335,7 @@
335335
expect(endpoints).to have_attributes(size: 1)
336336
expect(endpoints.first).to eq(
337337
path: "/webhook/json",
338-
handler: "JsonHandler",
338+
handler: "json_handler",
339339
method: "POST"
340340
)
341341
end
@@ -348,7 +348,7 @@
348348
let(:valid_config) do
349349
{
350350
"path" => "/webhook/valid",
351-
"handler" => "ValidHandler"
351+
"handler" => "valid_handler"
352352
}
353353
end
354354

@@ -364,7 +364,7 @@
364364
expect(endpoints).to have_attributes(size: 1)
365365
expect(endpoints.first).to eq(
366366
path: "/webhook/valid",
367-
handler: "ValidHandler"
367+
handler: "valid_handler"
368368
)
369369
end
370370
it "allows environment variable setup" do

spec/unit/lib/hooks/core/config_validator_security_spec.rb

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@
88
context "with secure handler names" do
99
it "accepts valid handler names" do
1010
valid_configs = [
11-
{ path: "/webhook", handler: "MyHandler" },
12-
{ path: "/webhook", handler: "GitHubHandler" },
13-
{ path: "/webhook", handler: "Team1Handler" },
14-
{ path: "/webhook", handler: "WebhookHandler" },
15-
{ path: "/webhook", handler: "CustomWebhookHandler" },
16-
{ path: "/webhook", handler: "Handler123" },
17-
{ path: "/webhook", handler: "My_Handler" }
11+
{ path: "/webhook", handler: "my_handler" },
12+
{ path: "/webhook", handler: "github_handler" },
13+
{ path: "/webhook", handler: "team_1_handler" },
14+
{ path: "/webhook", handler: "webhook_handler" },
15+
{ path: "/webhook", handler: "custom_webhook_handler" },
16+
{ path: "/webhook", handler: "handler_123" },
17+
{ path: "/webhook", handler: "my_handler" }
1818
]
1919

2020
valid_configs.each do |config|
@@ -26,7 +26,9 @@
2626

2727
it "rejects dangerous system class names" do
2828
dangerous_configs = Hooks::Security::DANGEROUS_CLASSES.map do |class_name|
29-
{ path: "/webhook", handler: class_name }
29+
# Convert PascalCase to snake_case for config
30+
snake_case_name = class_name.gsub(/([A-Z])/, '_\1').downcase.sub(/^_/, '')
31+
{ path: "/webhook", handler: snake_case_name }
3032
end
3133

3234
dangerous_configs.each do |config|
@@ -38,14 +40,16 @@
3840

3941
it "rejects handler names with invalid format" do
4042
invalid_configs = [
41-
{ path: "/webhook", handler: "handler" }, # lowercase start
42-
{ path: "/webhook", handler: "123Handler" }, # number start
43-
{ path: "/webhook", handler: "_Handler" }, # underscore start
44-
{ path: "/webhook", handler: "Handler$Test" }, # special characters
45-
{ path: "/webhook", handler: "Handler.Test" }, # dots
46-
{ path: "/webhook", handler: "Handler/Test" }, # slashes
47-
{ path: "/webhook", handler: "Handler Test" }, # spaces
48-
{ path: "/webhook", handler: "Handler\nTest" } # newlines
43+
{ path: "/webhook", handler: "Handler" }, # uppercase start
44+
{ path: "/webhook", handler: "123handler" }, # number start
45+
{ path: "/webhook", handler: "_handler" }, # underscore start
46+
{ path: "/webhook", handler: "handler$test" }, # special characters
47+
{ path: "/webhook", handler: "handler.test" }, # dots
48+
{ path: "/webhook", handler: "handler/test" }, # slashes
49+
{ path: "/webhook", handler: "handler test" }, # spaces
50+
{ path: "/webhook", handler: "handler\ntest" }, # newlines
51+
{ path: "/webhook", handler: "handlerTest" }, # camelCase
52+
{ path: "/webhook", handler: "HandlerTest" } # PascalCase
4953
]
5054

5155
invalid_configs.each do |config|
@@ -88,9 +92,9 @@
8892
context "with endpoint arrays" do
8993
it "validates all endpoints in an array and reports the problematic one" do
9094
endpoints = [
91-
{ path: "/webhook1", handler: "ValidHandler" },
92-
{ path: "/webhook2", handler: "File" }, # This should fail
93-
{ path: "/webhook3", handler: "AnotherValidHandler" }
95+
{ path: "/webhook1", handler: "valid_handler" },
96+
{ path: "/webhook2", handler: "File" }, # This should fail (PascalCase)
97+
{ path: "/webhook3", handler: "another_valid_handler" }
9498
]
9599

96100
expect do

0 commit comments

Comments
 (0)