#!/usr/bin/env ruby # frozen_string_literal: true require 'minitest/autorun' require_relative 'notify_backup' class ByteFormatterTest < Minitest::Test def test_gigabytes assert_equal '4.50 GB', ByteFormatter.format(4_831_838_208) end def test_megabytes assert_equal '823.00 MB', ByteFormatter.format(862_978_048) end def test_kilobytes assert_equal '12.00 KB', ByteFormatter.format(12_288) end def test_bytes_below_one_kb assert_equal '512 B', ByteFormatter.format(512) end def test_nil_returns_nil assert_nil ByteFormatter.format(nil) end def test_string_integer_is_coerced assert_equal '1.00 GB', ByteFormatter.format('1073741824') end end class PayloadBuilderTest < Minitest::Test def base_options { machine: 'desktop', destination: 'synology', status: 'success' } end def test_builds_required_fields payload = PayloadBuilder.new(base_options).build assert_equal 'desktop', payload[:machine] assert_equal 'synology', payload[:destination] assert_equal 'success', payload[:status] assert_match(/\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z\z/, payload[:timestamp]) end def test_sends_raw_integer_bytes payload = PayloadBuilder.new(base_options.merge(bytes: 1_073_741_824)).build assert_equal 1_073_741_824, payload[:bytes_transferred] end def test_omits_nil_optional_fields payload = PayloadBuilder.new(base_options).build refute payload.key?(:bytes_transferred) refute payload.key?(:snapshot_id) refute payload.key?(:log_path) refute payload.key?(:notes) end def test_includes_all_optional_fields_when_present opts = base_options.merge( bytes: 1_048_576, snapshot: 'abc12345', log_path: '/home/jared/.local/log/restic/2026-05-07.log', notes: 'snapshot abc12345 saved' ) payload = PayloadBuilder.new(opts).build assert_equal 1_048_576, payload[:bytes_transferred] assert_equal 'abc12345', payload[:snapshot_id] assert_equal '/home/jared/.local/log/restic/2026-05-07.log', payload[:log_path] assert_equal 'snapshot abc12345 saved', payload[:notes] end def test_raises_on_invalid_status assert_raises(ArgumentError) { PayloadBuilder.new(base_options.merge(status: 'unknown')).build } end def test_all_valid_statuses_accepted %w[success failure skipped].each do |s| assert PayloadBuilder.new(base_options.merge(status: s)).build 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 class FakeClient def initialize @posted = false @payload = nil end def post(payload) @posted = true @payload = payload end def posted_payload @payload end def was_called? @posted end end class BackupNotifierTest < Minitest::Test def base_options { machine: 'desktop', destination: 'synology', status: 'success' } end def test_notify_calls_client_with_a_hash_payload fake_client = FakeClient.new BackupNotifier.new(options: base_options, client: fake_client).notify assert fake_client.was_called? assert_instance_of Hash, fake_client.posted_payload end def test_from_env_returns_a_backup_notifier ENV['N8N_SWANSONCLOUD_URL'] = 'https://n8n.example.com' ENV['N8N_SWANSONCLOUD_BACKUP_AUTH_TOKEN'] = 'test-token' notifier = BackupNotifier.from_env(base_options) assert_instance_of BackupNotifier, notifier ensure ENV.delete('N8N_SWANSONCLOUD_URL') ENV.delete('N8N_SWANSONCLOUD_BACKUP_AUTH_TOKEN') end def test_from_env_raises_when_env_var_missing ENV.delete('N8N_SWANSONCLOUD_URL') assert_raises(KeyError) { BackupNotifier.from_env(base_options) } end end