From 3e49704bdba36cad99525f2d68f20c9f572a49b5 Mon Sep 17 00:00:00 2001 From: jared Date: Thu, 7 May 2026 11:17:53 -0400 Subject: [PATCH] feat(backup-monitor): implement WebhookClient with injected HTTP adapter and tests --- scripts/notify_backup.rb | 26 ++++++++++++++++ scripts/notify_backup_test.rb | 56 +++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/scripts/notify_backup.rb b/scripts/notify_backup.rb index ebfd2af..4208159 100644 --- a/scripts/notify_backup.rb +++ b/scripts/notify_backup.rb @@ -86,6 +86,32 @@ class PayloadBuilder end class WebhookClient + def initialize(base_url:, auth_token:, http_adapter: Net::HTTP) + @base_url = base_url + @auth_token = auth_token + @http_adapter = http_adapter + end + + def post(payload) + uri = URI("#{@base_url}/webhook/backup-notify") + request = build_request(uri, payload) + + response = @http_adapter.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http| + http.request(request) + end + + raise "HTTP #{response.code}: #{response.body}" unless response.is_a?(Net::HTTPSuccess) + end + + private + + def build_request(uri, payload) + req = Net::HTTP::Post.new(uri) + req['Content-Type'] = 'application/json' + req['Authorization'] = "Bearer #{@auth_token}" + req.body = JSON.generate(payload) + req + end end class BackupNotifier diff --git a/scripts/notify_backup_test.rb b/scripts/notify_backup_test.rb index 83c78b7..76dfca4 100644 --- a/scripts/notify_backup_test.rb +++ b/scripts/notify_backup_test.rb @@ -80,3 +80,59 @@ class PayloadBuilderTest < Minitest::Test end end end + +# Stub HTTP adapter for WebhookClient tests — replaces Net::HTTP entirely. +# Sandi Metz: test the interface, inject the dependency, don't touch globals. +class FakeHttpAdapter + def initialize(response) + @response = response + end + + def start(_host, _port, use_ssl: false) + yield FakeHttpSession.new(@response) + end +end + +class FakeHttpSession + def initialize(response) + @response = response + end + + def request(_req) + @response + end +end + +class WebhookClientTest < Minitest::Test + def make_success_response + res = Net::HTTPSuccess.new('1.1', '200', 'OK') + res.instance_variable_set(:@read, true) + res + end + + def make_error_response + res = Net::HTTPServerError.new('1.1', '500', 'Internal Server Error') + res.instance_variable_set(:@body, 'something broke') + res.instance_variable_set(:@read, true) + res + end + + def make_client(response) + WebhookClient.new( + base_url: 'https://n8n.example.com', + auth_token: 'test-token', + http_adapter: FakeHttpAdapter.new(response) + ) + end + + def test_post_succeeds_on_200 + client = make_client(make_success_response) + client.post({ machine: 'desktop', status: 'success' }) # no error raised + end + + def test_post_raises_on_500 + client = make_client(make_error_response) + err = assert_raises(RuntimeError) { client.post({}) } + assert_match(/HTTP 500/, err.message) + end +end