webhukhs 0.7.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.yard-lint.yml +1 -0
  3. data/AGENTS.md +9 -17
  4. data/Appraisals +2 -2
  5. data/CHANGELOG.md +18 -0
  6. data/README.md +106 -10
  7. data/Rakefile +7 -0
  8. data/example/Gemfile +5 -6
  9. data/example/Gemfile.lock +260 -141
  10. data/example/app/webhooks/acme_handler.rb +16 -0
  11. data/example/app/webhooks/flaky_handler.rb +8 -0
  12. data/example/app/webhooks/signed_handler.rb +10 -0
  13. data/example/config/initializers/webhukhs.rb +4 -3
  14. data/example/config/routes.rb +3 -0
  15. data/example/db/migrate/20240601000000_add_headers_to_webhukhs_webhooks.rb +7 -0
  16. data/example/db/schema.rb +2 -1
  17. data/example/db/seeds.rb +71 -7
  18. data/gemfiles/{rails_7.1.gemfile → rails_7.2.gemfile} +1 -4
  19. data/gemfiles/rails_7.2.gemfile.lock +477 -0
  20. data/gemfiles/rails_8.0.gemfile +0 -3
  21. data/gemfiles/rails_8.0.gemfile.lock +74 -4
  22. data/gemfiles/rails_8.1.gemfile +0 -3
  23. data/gemfiles/rails_8.1.gemfile.lock +74 -4
  24. data/gemfiles/rails_master.gemfile +0 -3
  25. data/lib/webhukhs/admin/application_controller.rb +32 -0
  26. data/lib/webhukhs/admin/assets/admin.css +476 -0
  27. data/lib/webhukhs/admin/assets/admin.js +111 -0
  28. data/lib/webhukhs/admin/config/routes.rb +6 -0
  29. data/lib/webhukhs/admin/engine.rb +20 -0
  30. data/lib/webhukhs/admin/received_webhook_presenter.rb +63 -0
  31. data/lib/webhukhs/admin/received_webhooks_controller.rb +62 -0
  32. data/lib/webhukhs/admin/view_helper.rb +50 -0
  33. data/lib/webhukhs/admin/views/layouts/webhukhs/admin/application.html.erb +22 -0
  34. data/lib/webhukhs/admin/views/webhukhs/admin/received_webhooks/index.html.erb +51 -0
  35. data/lib/webhukhs/admin/views/webhukhs/admin/received_webhooks/show.html.erb +55 -0
  36. data/lib/webhukhs/admin.rb +17 -0
  37. data/lib/webhukhs/engine.rb +0 -4
  38. data/lib/webhukhs/models/received_webhook.rb +3 -0
  39. data/lib/webhukhs/version.rb +1 -1
  40. data/test/admin_test.rb +354 -0
  41. data/test/base_handler_test.rb +3 -3
  42. data/test/browser/admin_click_through.rb +212 -0
  43. data/test/processing_job_test.rb +15 -28
  44. data/test/received_webhook_test.rb +16 -0
  45. data/test/test_app.rb +2 -0
  46. data/test/test_helper.rb +9 -6
  47. data/test/webhukhs_test.rb +20 -62
  48. metadata +165 -9
  49. data/example/.ruby-version +0 -1
  50. data/gemfiles/rails_7.1.gemfile.lock +0 -412
  51. data/lib/tasks/webhukhs_tasks.rake +0 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 83faf212a808c37ee8d293dc4e1d74d82d8ce0a7fb653d3850ee01ff9da7000b
4
- data.tar.gz: 4d599377df025e4bd52c885efd5067127e50876eef1be8a9ee3117960d8bcf14
3
+ metadata.gz: 99b94333e5ec2959d8096f909a5370fccc5b68b9388e0a7028584a5cb334d660
4
+ data.tar.gz: 85c0dfd6138967c9c9f0fca36666ed07f7a9b714a608c34b13ed725d73dff67f
5
5
  SHA512:
6
- metadata.gz: a0834f112be61de4bf50a6a99d8eee1ebd439e11c2a4ab26fe2e178d4ba70baeee4c62a06ea13fac44f2797bf5d7db40689249f7252cc3e5038d76f4997fdac2
7
- data.tar.gz: 4070b8ad54761f7a888659c52a5284b660f86e729f645a8b1cc2658193b408b1852a4c6551147bd4ffe07f2c55f37dfb7b5aa8401388af44a2f762f1af973ccc
6
+ metadata.gz: 1ee87b2fb85e85a69493977d30cc21bbba0831d5b69973d560d4229260fdd7357a1a343c7da3634b503817827632e7f6ab8c1f5b92cd081030f11c68a8086053
7
+ data.tar.gz: e2820a7337b24bf51cc85e400889969f4744102c3ddc3948df9ae051d2baa96af6a8ae6ffad329cac90952f9d32362232ee2cd2a812dd7059fb04b9fabcb5ed1
data/.yard-lint.yml CHANGED
@@ -11,6 +11,7 @@ AllValidators:
11
11
  # Global file exclusion patterns
12
12
  Exclude:
13
13
  - '\.git'
14
+ - 'lib/webhukhs/engine.rb'
14
15
  - 'vendor/**/*'
15
16
  - 'node_modules/**/*'
16
17
  - 'spec/**/*'
data/AGENTS.md CHANGED
@@ -1,21 +1,25 @@
1
- You are working in a Ruby project that uses mutation testing.
1
+ You are working in a Ruby project that uses mutation testing, but also tracks line and branch coverage.
2
2
 
3
3
  ## Goal
4
4
 
5
- Achieve 100% mutation coverage. Verify with:
5
+ Achieve 100% line, branch and mutation coverage. Verify with:
6
+
7
+ ```
8
+ SIMPLECOV=1 bundle exec rspec
9
+ ```
6
10
 
7
11
  ```
8
12
  bundle exec mutant run
9
13
  ```
10
14
 
11
- When iterating, prefer `--fail-fast` so you address one surviving
15
+ When iterating mutant coverage, prefer `--fail-fast` so you address one surviving
12
16
  mutant at a time:
13
17
 
14
18
  ```
15
19
  bundle exec mutant run --fail-fast
16
20
  ```
17
21
 
18
- ## When you find an alive mutation
22
+ ## When you find coverage issue
19
23
 
20
24
  Decide which bucket it falls into:
21
25
 
@@ -28,24 +32,12 @@ Decide which bucket it falls into:
28
32
  Decide between A) and B) before changing anything. If unsure, ask
29
33
  the user.
30
34
 
31
- ## What you may change
32
-
33
- - `lib/age.rb` -- the implementation. Multiple designs are valid.
34
- - `spec/age_spec.rb` -- the tests. Some existing tests are bad and
35
- may be rewritten or replaced.
36
-
37
35
  ## Constraints
38
36
 
39
- - Line coverage must stay at 100%. Verify with:
40
-
41
- ```
42
- SIMPLECOV=1 bundle exec rspec
43
- ```
44
-
45
37
  - You may not skip mutants by configuring mutant to ignore them.
46
38
  No `expressions:` filters, no `coverage_criteria:` tweaks.
47
39
  - You may not use `send` or `__send__` to invoke private methods
48
- in tests just to satisfy mutant.
40
+ in tests just to satisfy coverage requirement.
49
41
  - You may not stub or mock the system under test (`Age`).
50
42
 
51
43
  ## Done
data/Appraisals CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- appraise "rails-7.1" do
4
- gem "rails", "~> 7.1.0"
3
+ appraise "rails-7.2" do
4
+ gem "rails", "~> 7.2.0"
5
5
  end
6
6
 
7
7
  appraise "rails-8.1" do
data/CHANGELOG.md CHANGED
@@ -5,6 +5,24 @@ This format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
5
5
 
6
6
  ## Unreleased
7
7
 
8
+ ## 0.8.1
9
+
10
+ ### Fixed
11
+ - Sort the admin listing by ID by default to avoid a full-table sort.
12
+
13
+ ## 0.8.0
14
+ ## Added
15
+ - An optional, read-only admin engine (`require "webhukhs/admin"`, mount `Webhukhs::Admin::Engine`) to list,
16
+ sort, filter and inspect received webhooks
17
+ - `bin/dev`, which runs the `example` app - now with the admin mounted, more handlers and seed data
18
+
19
+ ## Fixed
20
+ - Form-encoded webhooks were stored with an empty body: Rails had already read the body stream to parse
21
+ the params by the time Webhukhs got to it
22
+
23
+ ## Changed
24
+ - Rails 7.2 or newer is now required (and with it Ruby 3.1), and the test matrix runs Rails 7.2 instead of 7.1
25
+
8
26
  ## 0.7.0
9
27
  ## Added
10
28
  - Codebase is fully mutant tested now and test coverage increased
data/README.md CHANGED
@@ -19,32 +19,124 @@ Generate migrations and initializer file.
19
19
 
20
20
  `bin/rails g webhukhs:install`
21
21
 
22
- Mount webhukhs engine in your routes.
22
+ Mount webhukhs engine in `config/routes.rb` file (simple approach, with only one dynamic route showing up in `rails routes`)
23
23
 
24
24
  ```ruby
25
25
  mount Webhukhs::Engine, at: "/webhooks"
26
26
  ```
27
27
 
28
- Define a class for your first handler (let's call it `ExampleHandler`) and inherit it from `Webhukhs::BaseHandler`. We recommend `app/webhooks`, but any place known to autoloading will do. Add these to your `webhukhs.rb` config file:
28
+ Advanced mounting approach (all webhooks paths will be shown in `rails routes`)
29
+
30
+ ```ruby
31
+ Webhukhs.configuration.active_handlers.each_key do |service_id|
32
+ post "/webhooks/#{service_id}",
33
+ to: "webhukhs/receive_webhooks#create",
34
+ defaults: { service_id: service_id },
35
+ as: :"#{service_id.tr('-', '_')}_webhook"
36
+ end
37
+ ```
38
+
39
+ Define a class for your first handler (let's call it `ExampleHandler`) and inherit it from `Webhukhs::BaseHandler`. We recommend `app/webhooks`, but any place known to autoloading will do.
40
+
41
+ ```ruby
42
+ # app/webhooks/example_handler.rb
43
+ class ExampleHandler < Webhukhs::BaseHandler
44
+ # Called asynchronously to process a stored webhook.
45
+ def process(received_webhook)
46
+ payload = JSON.parse(received_webhook.body, symbolize_names: true)
47
+ # handle payload...
48
+ end
49
+
50
+ # Optional: verify the request signature before processing.
51
+ # Return false to mark the webhook as failed_validation.
52
+ def valid?(action_dispatch_request)
53
+ action_dispatch_request.headers["X-Secret-Token"] == ENV["EXAMPLE_WEBHOOK_SECRET"]
54
+ end
55
+
56
+ # Optional: use a sender-supplied ID for deduplication.
57
+ def extract_event_id_from_request(action_dispatch_request)
58
+ action_dispatch_request.headers["X-Event-Id"] || SecureRandom.uuid
59
+ end
60
+ end
61
+ ```
62
+
63
+ Add the handler to your `webhukhs.rb` config file:
29
64
 
30
65
  ```ruby
31
66
  Webhukhs.configure do |config|
32
67
  config.active_handlers = {
33
- "test-handler" => "WebhookTestHandler"
68
+ "example" => "ExampleHandler"
34
69
  }
35
70
  end
36
71
  ```
37
72
 
38
- ## Examples
39
- - `example` folder contains a demo app with engine fully configured.
40
- - We provide a number of webhook handlers which demonstrate certain features of Webhukhs. You will find them in `handler-examples`.
73
+ Now you will be able to accept webhooks on `/webhooks/example` path.
41
74
 
42
- ## Requirements
75
+ ## Admin
76
+
77
+ Webhukhs comes with a read-only admin for looking at the webhooks you have received: a sortable,
78
+ filterable list, and a page per webhook with its body (pretty-printed when it is JSON) and the
79
+ request headers it arrived with. It is not loaded by default - require it in your Gemfile (it has to be
80
+ loaded before the app boots, so `config/routes.rb` is too late):
81
+
82
+ ```ruby
83
+ gem "webhukhs", require: ["webhukhs", "webhukhs/admin"]
84
+ ```
43
85
 
44
- This project depends on two dependencies:
86
+ and mount it:
87
+
88
+ ```ruby
89
+ # config/routes.rb
90
+ Rails.application.routes.draw do
91
+ mount Webhukhs::Admin::Engine, at: "/admin/webhooks"
92
+ end
93
+ ```
45
94
 
46
- - Ruby >= 3.0
47
- - Rails >= 7.0
95
+ The admin does no authentication of its own, and webhook bodies tend to carry personal data and
96
+ secrets - so mount it behind whatever guards the rest of your admin. For example, with Devise:
97
+
98
+ ```ruby
99
+ authenticate :user, ->(user) { user.admin? } do
100
+ mount Webhukhs::Admin::Engine, at: "/admin/webhooks"
101
+ end
102
+ ```
103
+
104
+ or with HTTP basic auth:
105
+
106
+ ```ruby
107
+ webhukhs_admin = Rack::Builder.new do
108
+ use Rack::Auth::Basic do |username, password|
109
+ ActiveSupport::SecurityUtils.secure_compare(username, ENV.fetch("ADMIN_USER")) &
110
+ ActiveSupport::SecurityUtils.secure_compare(password, ENV.fetch("ADMIN_PASSWORD"))
111
+ end
112
+ run Webhukhs::Admin::Engine
113
+ end
114
+ mount webhukhs_admin, at: "/admin/webhooks"
115
+ ```
116
+
117
+ The admin brings its own CSS and JS, served by [Assiette](https://github.com/julik/assiette) from
118
+ inside the engine - it does not touch your asset pipeline (Propshaft, Sprockets or anything else) and needs
119
+ no build step.
120
+
121
+ To try it out, run `bin/dev` from a checkout of this repository. It starts the app in `example`
122
+ with some webhooks seeded, and the admin at http://localhost:3000/admin/webhukhs.
123
+
124
+ The list defaults to descending ID order using the existing primary-key index; no extra migration is
125
+ needed. Click **Received** to sort by timestamp instead.
126
+
127
+ **We strongly recommend against UUIDv4 primary keys.** Use UUIDv7 or auto-incrementing integer IDs
128
+ instead. UUIDv4 IDs are random, so sorting by ID does not reflect creation time, and inserts have
129
+ poorer index locality. Webhukhs generates UUIDv7 when Ruby supports it, but falls back to UUIDv4
130
+ otherwise; use a Ruby with UUIDv7 support if you need UUID primary keys.
131
+
132
+ ## More Examples
133
+ - `example` folder contains a demo app with the engine and the admin fully configured - run it with `bin/dev`.
134
+ - We provide a number of webhook handlers which demonstrate certain features of Webhukhs. You will find them in `handler-examples`.
135
+
136
+ ## Requirements
137
+
138
+ - Ruby >= 3.1
139
+ - Rails >= 7.2
48
140
 
49
141
  ## Error reporter
50
142
 
@@ -64,6 +156,10 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
64
156
 
65
157
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
66
158
 
159
+ The admin also has a click-through test in a real (headless) Chrome, which `rake test` leaves out.
160
+ Run it with `bundle exec rake test:browser` - it needs Chrome or Chromium installed, and finds it
161
+ by itself or through `BROWSER_PATH`.
162
+
67
163
  ## Contributing
68
164
 
69
165
  Bug reports and pull requests are welcome on GitHub at https://github.com/skatkov/webhukhs.
data/Rakefile CHANGED
@@ -23,4 +23,11 @@ Rake::TestTask.new(:test) do |t|
23
23
  end
24
24
  end
25
25
 
26
+ # Needs Chrome, so it stays out of the default task
27
+ Rake::TestTask.new("test:browser") do |t|
28
+ t.libs << "test"
29
+ t.libs << "lib"
30
+ t.test_files = FileList["test/browser/**/*.rb"]
31
+ end
32
+
26
33
  task default: [:test, :standard]
data/example/Gemfile CHANGED
@@ -3,23 +3,22 @@
3
3
  source "https://rubygems.org"
4
4
  git_source(:github) { |repo| "https://github.com/#{repo}.git" }
5
5
 
6
- ruby "3.2.2"
7
-
8
6
  # Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
9
- gem "rails", ">= 7.0.8.1"
7
+ gem "rails", ">= 7.2"
10
8
 
11
9
  # The modern asset pipeline for Rails [https://github.com/rails/propshaft]
12
10
  gem "propshaft"
13
11
 
14
12
  # Use sqlite3 as the database for Active Record
15
- gem "sqlite3", "~> 1.4"
13
+ gem "sqlite3", ">= 2.1"
16
14
 
17
15
  # Use the Puma web server [https://github.com/puma/puma]
18
- gem "puma", "~> 5.0"
16
+ gem "puma", ">= 6.0"
19
17
 
20
18
  # Windows does not include zoneinfo files, so bundle the tzinfo-data gem
21
19
  gem "tzinfo-data", platforms: %i[mingw mswin x64_mingw jruby]
22
- gem "webhukhs", path: "../"
20
+ # The admin is optional, and has to be required before the app boots
21
+ gem "webhukhs", path: "../", require: ["webhukhs", "webhukhs/admin"]
23
22
 
24
23
  group :development, :test do
25
24
  # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem