webhukhs 0.6.0 → 0.8.0
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.
- checksums.yaml +4 -4
- data/.standard.yml +2 -0
- data/.yard-lint.yml +296 -0
- data/AGENTS.md +56 -0
- data/Appraisals +6 -2
- data/CHANGELOG.md +20 -0
- data/README.md +101 -11
- data/Rakefile +7 -0
- data/config/mutant.rb +37 -0
- data/config/mutant.yml +25 -0
- data/example/Gemfile +5 -6
- data/example/Gemfile.lock +260 -141
- data/example/app/webhooks/acme_handler.rb +16 -0
- data/example/app/webhooks/flaky_handler.rb +8 -0
- data/example/app/webhooks/signed_handler.rb +10 -0
- data/example/config/initializers/webhukhs.rb +4 -3
- data/example/config/routes.rb +3 -0
- data/example/db/migrate/20240601000000_add_headers_to_webhukhs_webhooks.rb +7 -0
- data/example/db/schema.rb +2 -1
- data/example/db/seeds.rb +71 -7
- data/gemfiles/{rails_7.1.gemfile → rails_7.2.gemfile} +1 -4
- data/gemfiles/rails_7.2.gemfile.lock +477 -0
- data/gemfiles/rails_8.0.gemfile +0 -3
- data/gemfiles/rails_8.0.gemfile.lock +74 -4
- data/gemfiles/rails_8.1.gemfile +0 -3
- data/gemfiles/rails_8.1.gemfile.lock +74 -4
- data/gemfiles/rails_master.gemfile +7 -0
- data/lib/webhukhs/admin/application_controller.rb +32 -0
- data/lib/webhukhs/admin/assets/admin.css +476 -0
- data/lib/webhukhs/admin/assets/admin.js +111 -0
- data/lib/webhukhs/admin/config/routes.rb +6 -0
- data/lib/webhukhs/admin/engine.rb +20 -0
- data/lib/webhukhs/admin/received_webhook_presenter.rb +63 -0
- data/lib/webhukhs/admin/received_webhooks_controller.rb +60 -0
- data/lib/webhukhs/admin/view_helper.rb +50 -0
- data/lib/webhukhs/admin/views/layouts/webhukhs/admin/application.html.erb +22 -0
- data/lib/webhukhs/admin/views/webhukhs/admin/received_webhooks/index.html.erb +50 -0
- data/lib/webhukhs/admin/views/webhukhs/admin/received_webhooks/show.html.erb +55 -0
- data/lib/webhukhs/admin.rb +17 -0
- data/lib/webhukhs/base_handler.rb +9 -6
- data/lib/webhukhs/controllers/receive_webhooks_controller.rb +26 -7
- data/lib/webhukhs/engine.rb +1 -4
- data/lib/webhukhs/install_generator.rb +9 -2
- data/lib/webhukhs/jobs/processing_job.rb +13 -12
- data/lib/webhukhs/models/received_webhook.rb +21 -9
- data/lib/webhukhs/version.rb +1 -1
- data/lib/webhukhs.rb +9 -0
- data/test/admin_test.rb +334 -0
- data/test/base_handler_test.rb +125 -0
- data/test/browser/admin_click_through.rb +208 -0
- data/test/processing_job_test.rb +152 -12
- data/test/received_webhook_test.rb +260 -0
- data/test/test_app.rb +39 -15
- data/test/test_helper.rb +69 -0
- data/test/webhukhs_test.rb +111 -49
- metadata +173 -11
- data/example/.ruby-version +0 -1
- data/gemfiles/rails_7.1.gemfile.lock +0 -412
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cae5927b7426085a8e140f6bdb4565423d43d1b72c65b65c9602b72bd81a1338
|
|
4
|
+
data.tar.gz: e77b5321fbd8b814b3d6265da104ec9b4e77ffc36a2d0b0c62e67ae1c1f3ff05
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 40519e7b2ca3b8c8e4bbf924f78d86269f6f4b68b1a72f3a7f963ec87fde656f49db69c6268de76f19b22ec762e3e14078c9839f44e080b41bc1e99131ecb449
|
|
7
|
+
data.tar.gz: 2a8e469254e9f76b46068a9b06001157f570f33a6cab931c2ee23cada3c13eadbff765c368cba58e6a62a1d657db8d142a2f89d2b8fe94b4034eb76f45cb4e6c
|
data/.standard.yml
CHANGED
data/.yard-lint.yml
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
# YARD-Lint Configuration
|
|
2
|
+
# See https://github.com/mensfeld/yard-lint for documentation
|
|
3
|
+
|
|
4
|
+
# Global settings for all validators
|
|
5
|
+
AllValidators:
|
|
6
|
+
# YARD command-line options (applied to all validators by default)
|
|
7
|
+
YardOptions:
|
|
8
|
+
- --private
|
|
9
|
+
- --protected
|
|
10
|
+
|
|
11
|
+
# Global file exclusion patterns
|
|
12
|
+
Exclude:
|
|
13
|
+
- '\.git'
|
|
14
|
+
- 'lib/webhukhs/engine.rb'
|
|
15
|
+
- 'vendor/**/*'
|
|
16
|
+
- 'node_modules/**/*'
|
|
17
|
+
- 'spec/**/*'
|
|
18
|
+
- 'test/**/*'
|
|
19
|
+
- 'handler-examples/**/*'
|
|
20
|
+
- 'example/**/*'
|
|
21
|
+
|
|
22
|
+
# Exit code behavior (error, warning, convention, never)
|
|
23
|
+
FailOnSeverity: warning
|
|
24
|
+
|
|
25
|
+
# Minimum documentation coverage percentage (0-100)
|
|
26
|
+
# Fails if coverage is below this threshold
|
|
27
|
+
# MinCoverage: 80.0
|
|
28
|
+
|
|
29
|
+
# Diff mode settings
|
|
30
|
+
DiffMode:
|
|
31
|
+
# Default base ref for --diff (auto-detects main/master if not specified)
|
|
32
|
+
DefaultBaseRef: ~
|
|
33
|
+
|
|
34
|
+
# Documentation validators
|
|
35
|
+
Documentation/UndocumentedObjects:
|
|
36
|
+
Description: 'Checks for classes, modules, and methods without documentation.'
|
|
37
|
+
Enabled: true
|
|
38
|
+
Severity: warning
|
|
39
|
+
ExcludedMethods:
|
|
40
|
+
- 'initialize/0' # Exclude parameter-less initialize
|
|
41
|
+
- '/^_/' # Exclude private methods (by convention)
|
|
42
|
+
|
|
43
|
+
Documentation/UndocumentedMethodArguments:
|
|
44
|
+
Description: 'Checks for method parameters without @param tags.'
|
|
45
|
+
Enabled: true
|
|
46
|
+
Severity: warning
|
|
47
|
+
|
|
48
|
+
Documentation/UndocumentedBooleanMethods:
|
|
49
|
+
Description: 'Checks that question mark methods document their boolean return.'
|
|
50
|
+
Enabled: true
|
|
51
|
+
Severity: warning
|
|
52
|
+
|
|
53
|
+
Documentation/UndocumentedOptions:
|
|
54
|
+
Description: 'Detects methods with options hash parameters but no @option tags.'
|
|
55
|
+
Enabled: true
|
|
56
|
+
Severity: warning
|
|
57
|
+
|
|
58
|
+
Documentation/MarkdownSyntax:
|
|
59
|
+
Description: 'Detects common markdown syntax errors in documentation.'
|
|
60
|
+
Enabled: true
|
|
61
|
+
Severity: warning
|
|
62
|
+
|
|
63
|
+
Documentation/EmptyCommentLine:
|
|
64
|
+
Description: 'Detects empty comment lines at the start or end of documentation blocks.'
|
|
65
|
+
Enabled: true
|
|
66
|
+
Severity: convention
|
|
67
|
+
EnabledPatterns:
|
|
68
|
+
Leading: true
|
|
69
|
+
Trailing: true
|
|
70
|
+
|
|
71
|
+
Documentation/BlankLineBeforeDefinition:
|
|
72
|
+
Description: 'Detects blank lines between YARD documentation and method definition.'
|
|
73
|
+
Enabled: true
|
|
74
|
+
Severity: convention
|
|
75
|
+
OrphanedSeverity: convention
|
|
76
|
+
EnabledPatterns:
|
|
77
|
+
SingleBlankLine: true
|
|
78
|
+
OrphanedDocs: true
|
|
79
|
+
|
|
80
|
+
# Tags validators
|
|
81
|
+
Tags/Order:
|
|
82
|
+
Description: 'Enforces consistent ordering of YARD tags.'
|
|
83
|
+
Enabled: true
|
|
84
|
+
Severity: convention
|
|
85
|
+
EnforcedOrder:
|
|
86
|
+
- param
|
|
87
|
+
- option
|
|
88
|
+
- yield
|
|
89
|
+
- yieldparam
|
|
90
|
+
- yieldreturn
|
|
91
|
+
- return
|
|
92
|
+
- raise
|
|
93
|
+
- see
|
|
94
|
+
- example
|
|
95
|
+
- note
|
|
96
|
+
- todo
|
|
97
|
+
|
|
98
|
+
Tags/InvalidTypes:
|
|
99
|
+
Description: 'Validates type definitions in @param, @return, @option tags.'
|
|
100
|
+
Enabled: true
|
|
101
|
+
Severity: warning
|
|
102
|
+
ValidatedTags:
|
|
103
|
+
- param
|
|
104
|
+
- option
|
|
105
|
+
- return
|
|
106
|
+
|
|
107
|
+
Tags/TypeSyntax:
|
|
108
|
+
Description: 'Validates YARD type syntax using YARD parser.'
|
|
109
|
+
Enabled: true
|
|
110
|
+
Severity: warning
|
|
111
|
+
ValidatedTags:
|
|
112
|
+
- param
|
|
113
|
+
- option
|
|
114
|
+
- return
|
|
115
|
+
- yieldreturn
|
|
116
|
+
|
|
117
|
+
Tags/MeaninglessTag:
|
|
118
|
+
Description: 'Detects @param/@option tags on classes, modules, or constants.'
|
|
119
|
+
Enabled: true
|
|
120
|
+
Severity: warning
|
|
121
|
+
CheckedTags:
|
|
122
|
+
- param
|
|
123
|
+
- option
|
|
124
|
+
InvalidObjectTypes:
|
|
125
|
+
- class
|
|
126
|
+
- module
|
|
127
|
+
- constant
|
|
128
|
+
|
|
129
|
+
Tags/CollectionType:
|
|
130
|
+
Description: 'Validates Hash collection syntax consistency.'
|
|
131
|
+
Enabled: true
|
|
132
|
+
Severity: convention
|
|
133
|
+
EnforcedStyle: long # 'long' for Hash{K => V} (YARD standard), 'short' for {K => V}
|
|
134
|
+
ValidatedTags:
|
|
135
|
+
- param
|
|
136
|
+
- option
|
|
137
|
+
- return
|
|
138
|
+
- yieldreturn
|
|
139
|
+
|
|
140
|
+
Tags/TagTypePosition:
|
|
141
|
+
Description: 'Validates type annotation position in tags.'
|
|
142
|
+
Enabled: true
|
|
143
|
+
Severity: convention
|
|
144
|
+
CheckedTags:
|
|
145
|
+
- param
|
|
146
|
+
- option
|
|
147
|
+
# EnforcedStyle: 'type_after_name' (YARD standard: @param name [Type])
|
|
148
|
+
# or 'type_first' (@param [Type] name)
|
|
149
|
+
EnforcedStyle: type_after_name
|
|
150
|
+
|
|
151
|
+
Tags/ApiTags:
|
|
152
|
+
Description: 'Enforces @api tags on public objects.'
|
|
153
|
+
Enabled: false # Opt-in validator
|
|
154
|
+
Severity: warning
|
|
155
|
+
AllowedApis:
|
|
156
|
+
- public
|
|
157
|
+
- private
|
|
158
|
+
- internal
|
|
159
|
+
|
|
160
|
+
Tags/OptionTags:
|
|
161
|
+
Description: 'Requires @option tags for methods with options parameters.'
|
|
162
|
+
Enabled: true
|
|
163
|
+
Severity: warning
|
|
164
|
+
|
|
165
|
+
Tags/ExampleSyntax:
|
|
166
|
+
Description: 'Validates Ruby syntax in @example tags.'
|
|
167
|
+
Enabled: true
|
|
168
|
+
Severity: warning
|
|
169
|
+
|
|
170
|
+
Tags/RedundantParamDescription:
|
|
171
|
+
Description: 'Detects meaningless parameter descriptions that add no value.'
|
|
172
|
+
Enabled: true
|
|
173
|
+
Severity: convention
|
|
174
|
+
CheckedTags:
|
|
175
|
+
- param
|
|
176
|
+
- option
|
|
177
|
+
Articles:
|
|
178
|
+
- The
|
|
179
|
+
- the
|
|
180
|
+
- A
|
|
181
|
+
- a
|
|
182
|
+
- An
|
|
183
|
+
- an
|
|
184
|
+
MaxRedundantWords: 6
|
|
185
|
+
GenericTerms:
|
|
186
|
+
- object
|
|
187
|
+
- instance
|
|
188
|
+
- value
|
|
189
|
+
- data
|
|
190
|
+
- item
|
|
191
|
+
- element
|
|
192
|
+
EnabledPatterns:
|
|
193
|
+
ArticleParam: true
|
|
194
|
+
PossessiveParam: true
|
|
195
|
+
TypeRestatement: true
|
|
196
|
+
ParamToVerb: true
|
|
197
|
+
IdPattern: true
|
|
198
|
+
DirectionalDate: true
|
|
199
|
+
TypeGeneric: true
|
|
200
|
+
|
|
201
|
+
Tags/InformalNotation:
|
|
202
|
+
Description: 'Detects informal tag notation patterns like "Note:" instead of @note.'
|
|
203
|
+
Enabled: true
|
|
204
|
+
Severity: warning
|
|
205
|
+
CaseSensitive: false
|
|
206
|
+
RequireStartOfLine: true
|
|
207
|
+
Patterns:
|
|
208
|
+
Note: '@note'
|
|
209
|
+
Todo: '@todo'
|
|
210
|
+
TODO: '@todo'
|
|
211
|
+
FIXME: '@todo'
|
|
212
|
+
See: '@see'
|
|
213
|
+
See also: '@see'
|
|
214
|
+
Warning: '@deprecated'
|
|
215
|
+
Deprecated: '@deprecated'
|
|
216
|
+
Author: '@author'
|
|
217
|
+
Version: '@version'
|
|
218
|
+
Since: '@since'
|
|
219
|
+
Returns: '@return'
|
|
220
|
+
Raises: '@raise'
|
|
221
|
+
Example: '@example'
|
|
222
|
+
|
|
223
|
+
Tags/NonAsciiType:
|
|
224
|
+
Description: 'Detects non-ASCII characters in type annotations.'
|
|
225
|
+
Enabled: true
|
|
226
|
+
Severity: warning
|
|
227
|
+
ValidatedTags:
|
|
228
|
+
- param
|
|
229
|
+
- option
|
|
230
|
+
- return
|
|
231
|
+
- yieldreturn
|
|
232
|
+
- yieldparam
|
|
233
|
+
|
|
234
|
+
Tags/TagGroupSeparator:
|
|
235
|
+
Description: 'Enforces blank line separators between different YARD tag groups.'
|
|
236
|
+
Enabled: false # Opt-in validator
|
|
237
|
+
Severity: convention
|
|
238
|
+
TagGroups:
|
|
239
|
+
param: [param, option]
|
|
240
|
+
return: [return]
|
|
241
|
+
error: [raise, throws]
|
|
242
|
+
example: [example]
|
|
243
|
+
meta: [see, note, todo, deprecated, since, version, api]
|
|
244
|
+
yield: [yield, yieldparam, yieldreturn]
|
|
245
|
+
RequireAfterDescription: false
|
|
246
|
+
|
|
247
|
+
Tags/ForbiddenTags:
|
|
248
|
+
Description: 'Detects forbidden tag and type combinations.'
|
|
249
|
+
Enabled: false # Opt-in validator
|
|
250
|
+
Severity: convention
|
|
251
|
+
ForbiddenPatterns: []
|
|
252
|
+
# Example patterns:
|
|
253
|
+
# - Tag: return
|
|
254
|
+
# Types:
|
|
255
|
+
# - void
|
|
256
|
+
# - Tag: param
|
|
257
|
+
# Types:
|
|
258
|
+
# - Object
|
|
259
|
+
# - Tag: api # Forbids @api tag entirely (no Types = any occurrence)
|
|
260
|
+
|
|
261
|
+
# Warnings validators - catches YARD parser errors
|
|
262
|
+
Warnings/UnknownTag:
|
|
263
|
+
Description: 'Detects unknown YARD tags.'
|
|
264
|
+
Enabled: true
|
|
265
|
+
Severity: error
|
|
266
|
+
|
|
267
|
+
Warnings/UnknownDirective:
|
|
268
|
+
Description: 'Detects unknown YARD directives.'
|
|
269
|
+
Enabled: true
|
|
270
|
+
Severity: error
|
|
271
|
+
|
|
272
|
+
Warnings/InvalidTagFormat:
|
|
273
|
+
Description: 'Detects malformed tag syntax.'
|
|
274
|
+
Enabled: true
|
|
275
|
+
Severity: error
|
|
276
|
+
|
|
277
|
+
Warnings/InvalidDirectiveFormat:
|
|
278
|
+
Description: 'Detects malformed directive syntax.'
|
|
279
|
+
Enabled: true
|
|
280
|
+
Severity: error
|
|
281
|
+
|
|
282
|
+
Warnings/DuplicatedParameterName:
|
|
283
|
+
Description: 'Detects duplicate @param tags.'
|
|
284
|
+
Enabled: true
|
|
285
|
+
Severity: error
|
|
286
|
+
|
|
287
|
+
Warnings/UnknownParameterName:
|
|
288
|
+
Description: 'Detects @param tags for non-existent parameters.'
|
|
289
|
+
Enabled: true
|
|
290
|
+
Severity: error
|
|
291
|
+
|
|
292
|
+
# Semantic validators
|
|
293
|
+
Semantic/AbstractMethods:
|
|
294
|
+
Description: 'Ensures @abstract methods do not have real implementations.'
|
|
295
|
+
Enabled: true
|
|
296
|
+
Severity: warning
|
data/AGENTS.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
You are working in a Ruby project that uses mutation testing, but also tracks line and branch coverage.
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
Achieve 100% line, branch and mutation coverage. Verify with:
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
SIMPLECOV=1 bundle exec rspec
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
bundle exec mutant run
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
When iterating mutant coverage, prefer `--fail-fast` so you address one surviving
|
|
16
|
+
mutant at a time:
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
bundle exec mutant run --fail-fast
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## When you find coverage issue
|
|
23
|
+
|
|
24
|
+
Decide which bucket it falls into:
|
|
25
|
+
|
|
26
|
+
- **A) The code does too much** for what the tests ask for. The
|
|
27
|
+
surviving mutation reveals behavior that no test requires. The
|
|
28
|
+
fix is to simplify the implementation.
|
|
29
|
+
- **B) A test is missing.** The behavior is intentional but no test
|
|
30
|
+
observes it. The fix is to add a test.
|
|
31
|
+
|
|
32
|
+
Decide between A) and B) before changing anything. If unsure, ask
|
|
33
|
+
the user.
|
|
34
|
+
|
|
35
|
+
## What you may change
|
|
36
|
+
|
|
37
|
+
- `lib/age.rb` -- the implementation. Multiple designs are valid.
|
|
38
|
+
- `spec/age_spec.rb` -- the tests. Some existing tests are bad and
|
|
39
|
+
may be rewritten or replaced.
|
|
40
|
+
|
|
41
|
+
## Constraints
|
|
42
|
+
|
|
43
|
+
- You may not skip mutants by configuring mutant to ignore them.
|
|
44
|
+
No `expressions:` filters, no `coverage_criteria:` tweaks.
|
|
45
|
+
- You may not use `send` or `__send__` to invoke private methods
|
|
46
|
+
in tests just to satisfy coverage requirement.
|
|
47
|
+
- You may not stub or mock the system under test (`Age`).
|
|
48
|
+
|
|
49
|
+
## Done
|
|
50
|
+
|
|
51
|
+
You are done when both of these are green:
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
SIMPLECOV=1 bundle exec rspec
|
|
55
|
+
bundle exec mutant run
|
|
56
|
+
```
|
data/Appraisals
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
appraise "rails-7.
|
|
4
|
-
gem "rails", "~> 7.
|
|
3
|
+
appraise "rails-7.2" do
|
|
4
|
+
gem "rails", "~> 7.2.0"
|
|
5
5
|
end
|
|
6
6
|
|
|
7
7
|
appraise "rails-8.1" do
|
|
@@ -11,3 +11,7 @@ end
|
|
|
11
11
|
appraise "rails-8.0" do
|
|
12
12
|
gem "rails", "~> 8.0"
|
|
13
13
|
end
|
|
14
|
+
|
|
15
|
+
appraise "rails-master" do
|
|
16
|
+
gem "rails", github: "rails/rails", branch: "main"
|
|
17
|
+
end
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,26 @@ 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.0
|
|
9
|
+
## Added
|
|
10
|
+
- An optional, read-only admin engine (`require "webhukhs/admin"`, mount `Webhukhs::Admin::Engine`) to list,
|
|
11
|
+
sort, filter and inspect received webhooks
|
|
12
|
+
- `bin/dev`, which runs the `example` app - now with the admin mounted, more handlers and seed data
|
|
13
|
+
|
|
14
|
+
## Fixed
|
|
15
|
+
- Form-encoded webhooks were stored with an empty body: Rails had already read the body stream to parse
|
|
16
|
+
the params by the time Webhukhs got to it
|
|
17
|
+
|
|
18
|
+
## Changed
|
|
19
|
+
- 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
|
|
20
|
+
|
|
21
|
+
## 0.7.0
|
|
22
|
+
## Added
|
|
23
|
+
- Codebase is fully mutant tested now and test coverage increased
|
|
24
|
+
|
|
25
|
+
## Changed
|
|
26
|
+
- Codebase was simplified according to mutants
|
|
27
|
+
|
|
8
28
|
## 0.6.0
|
|
9
29
|
|
|
10
30
|
### Changed
|
data/README.md
CHANGED
|
@@ -19,30 +19,116 @@ Generate migrations and initializer file.
|
|
|
19
19
|
|
|
20
20
|
`bin/rails g webhukhs:install`
|
|
21
21
|
|
|
22
|
-
Mount webhukhs engine in
|
|
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
|
-
|
|
28
|
+
Advanced mounting approach (all webhooks paths will be shown in `rails routes`)
|
|
29
29
|
|
|
30
30
|
```ruby
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
|
34
37
|
```
|
|
35
38
|
|
|
36
|
-
|
|
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.
|
|
37
40
|
|
|
38
|
-
|
|
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
|
+
```
|
|
39
62
|
|
|
40
|
-
|
|
63
|
+
Add the handler to your `webhukhs.rb` config file:
|
|
64
|
+
|
|
65
|
+
```ruby
|
|
66
|
+
Webhukhs.configure do |config|
|
|
67
|
+
config.active_handlers = {
|
|
68
|
+
"example" => "ExampleHandler"
|
|
69
|
+
}
|
|
70
|
+
end
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Now you will be able to accept webhooks on `/webhooks/example` path.
|
|
74
|
+
|
|
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
|
+
```
|
|
85
|
+
|
|
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
|
+
```
|
|
94
|
+
|
|
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:
|
|
41
105
|
|
|
42
|
-
|
|
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.
|
|
43
123
|
|
|
44
|
-
|
|
45
|
-
-
|
|
124
|
+
## More Examples
|
|
125
|
+
- `example` folder contains a demo app with the engine and the admin fully configured - run it with `bin/dev`.
|
|
126
|
+
- We provide a number of webhook handlers which demonstrate certain features of Webhukhs. You will find them in `handler-examples`.
|
|
127
|
+
|
|
128
|
+
## Requirements
|
|
129
|
+
|
|
130
|
+
- Ruby >= 3.1
|
|
131
|
+
- Rails >= 7.2
|
|
46
132
|
|
|
47
133
|
## Error reporter
|
|
48
134
|
|
|
@@ -62,6 +148,10 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
|
62
148
|
|
|
63
149
|
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).
|
|
64
150
|
|
|
151
|
+
The admin also has a click-through test in a real (headless) Chrome, which `rake test` leaves out.
|
|
152
|
+
Run it with `bundle exec rake test:browser` - it needs Chrome or Chromium installed, and finds it
|
|
153
|
+
by itself or through `BROWSER_PATH`.
|
|
154
|
+
|
|
65
155
|
## Contributing
|
|
66
156
|
|
|
67
157
|
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/config/mutant.rb
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
5
|
+
project_root = File.expand_path("..", __dir__)
|
|
6
|
+
template_database = File.expand_path("test/development.sqlite3", project_root)
|
|
7
|
+
worker_database_dir = File.expand_path("tmp/mutant", project_root)
|
|
8
|
+
|
|
9
|
+
disconnect_database = lambda do
|
|
10
|
+
next unless defined?(ActiveRecord::Base)
|
|
11
|
+
next unless ActiveRecord::Base.connected?
|
|
12
|
+
|
|
13
|
+
ActiveRecord::Base.connection_pool.disconnect!
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
isolate_database = lambda do |index:|
|
|
17
|
+
disconnect_database.call
|
|
18
|
+
FileUtils.mkdir_p(worker_database_dir)
|
|
19
|
+
|
|
20
|
+
database = File.join(worker_database_dir, "worker-#{index}-#{Process.pid}.sqlite3")
|
|
21
|
+
FileUtils.cp(template_database, database)
|
|
22
|
+
|
|
23
|
+
ENV["DATABASE_URL"] = "sqlite3:#{database}"
|
|
24
|
+
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: database)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
hooks.register(:setup_integration_post) do
|
|
28
|
+
disconnect_database.call
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
hooks.register(:test_worker_process_start) do |index:|
|
|
32
|
+
isolate_database.call(index: index)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
hooks.register(:mutation_worker_process_start) do |index:|
|
|
36
|
+
isolate_database.call(index: index)
|
|
37
|
+
end
|
data/config/mutant.yml
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
usage: opensource
|
|
2
|
+
|
|
3
|
+
integration:
|
|
4
|
+
name: minitest
|
|
5
|
+
|
|
6
|
+
includes:
|
|
7
|
+
- lib
|
|
8
|
+
- test
|
|
9
|
+
|
|
10
|
+
environment_variables:
|
|
11
|
+
RAILS_ENV: test
|
|
12
|
+
|
|
13
|
+
requires:
|
|
14
|
+
- ./test/test_app.rb
|
|
15
|
+
|
|
16
|
+
matcher:
|
|
17
|
+
subjects:
|
|
18
|
+
- Webhukhs*
|
|
19
|
+
|
|
20
|
+
coverage_criteria:
|
|
21
|
+
timeout: true
|
|
22
|
+
process_abort: true
|
|
23
|
+
|
|
24
|
+
hooks:
|
|
25
|
+
- ./config/mutant.rb
|
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.
|
|
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", "
|
|
13
|
+
gem "sqlite3", ">= 2.1"
|
|
16
14
|
|
|
17
15
|
# Use the Puma web server [https://github.com/puma/puma]
|
|
18
|
-
gem "puma", "
|
|
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
|
-
|
|
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
|