webhukhs 0.5.0 → 0.7.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 +295 -0
- data/AGENTS.md +58 -0
- data/Appraisals +4 -0
- data/CHANGELOG.md +15 -0
- data/README.md +9 -7
- data/config/mutant.rb +37 -0
- data/config/mutant.yml +25 -0
- data/gemfiles/rails_master.gemfile +10 -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 -0
- data/lib/webhukhs/install_generator.rb +9 -2
- data/lib/webhukhs/jobs/processing_job.rb +24 -4
- data/lib/webhukhs/models/received_webhook.rb +33 -6
- data/lib/webhukhs/version.rb +1 -1
- data/lib/webhukhs.rb +9 -0
- data/test/base_handler_test.rb +125 -0
- data/test/processing_job_test.rb +195 -0
- data/test/received_webhook_test.rb +244 -0
- data/test/test_app.rb +37 -15
- data/test/test_helper.rb +66 -0
- data/test/webhukhs_test.rb +126 -10
- metadata +11 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 83faf212a808c37ee8d293dc4e1d74d82d8ce0a7fb653d3850ee01ff9da7000b
|
|
4
|
+
data.tar.gz: 4d599377df025e4bd52c885efd5067127e50876eef1be8a9ee3117960d8bcf14
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a0834f112be61de4bf50a6a99d8eee1ebd439e11c2a4ab26fe2e178d4ba70baeee4c62a06ea13fac44f2797bf5d7db40689249f7252cc3e5038d76f4997fdac2
|
|
7
|
+
data.tar.gz: 4070b8ad54761f7a888659c52a5284b660f86e729f645a8b1cc2658193b408b1852a4c6551147bd4ffe07f2c55f37dfb7b5aa8401388af44a2f762f1af973ccc
|
data/.standard.yml
CHANGED
data/.yard-lint.yml
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
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
|
+
- 'vendor/**/*'
|
|
15
|
+
- 'node_modules/**/*'
|
|
16
|
+
- 'spec/**/*'
|
|
17
|
+
- 'test/**/*'
|
|
18
|
+
- 'handler-examples/**/*'
|
|
19
|
+
- 'example/**/*'
|
|
20
|
+
|
|
21
|
+
# Exit code behavior (error, warning, convention, never)
|
|
22
|
+
FailOnSeverity: warning
|
|
23
|
+
|
|
24
|
+
# Minimum documentation coverage percentage (0-100)
|
|
25
|
+
# Fails if coverage is below this threshold
|
|
26
|
+
# MinCoverage: 80.0
|
|
27
|
+
|
|
28
|
+
# Diff mode settings
|
|
29
|
+
DiffMode:
|
|
30
|
+
# Default base ref for --diff (auto-detects main/master if not specified)
|
|
31
|
+
DefaultBaseRef: ~
|
|
32
|
+
|
|
33
|
+
# Documentation validators
|
|
34
|
+
Documentation/UndocumentedObjects:
|
|
35
|
+
Description: 'Checks for classes, modules, and methods without documentation.'
|
|
36
|
+
Enabled: true
|
|
37
|
+
Severity: warning
|
|
38
|
+
ExcludedMethods:
|
|
39
|
+
- 'initialize/0' # Exclude parameter-less initialize
|
|
40
|
+
- '/^_/' # Exclude private methods (by convention)
|
|
41
|
+
|
|
42
|
+
Documentation/UndocumentedMethodArguments:
|
|
43
|
+
Description: 'Checks for method parameters without @param tags.'
|
|
44
|
+
Enabled: true
|
|
45
|
+
Severity: warning
|
|
46
|
+
|
|
47
|
+
Documentation/UndocumentedBooleanMethods:
|
|
48
|
+
Description: 'Checks that question mark methods document their boolean return.'
|
|
49
|
+
Enabled: true
|
|
50
|
+
Severity: warning
|
|
51
|
+
|
|
52
|
+
Documentation/UndocumentedOptions:
|
|
53
|
+
Description: 'Detects methods with options hash parameters but no @option tags.'
|
|
54
|
+
Enabled: true
|
|
55
|
+
Severity: warning
|
|
56
|
+
|
|
57
|
+
Documentation/MarkdownSyntax:
|
|
58
|
+
Description: 'Detects common markdown syntax errors in documentation.'
|
|
59
|
+
Enabled: true
|
|
60
|
+
Severity: warning
|
|
61
|
+
|
|
62
|
+
Documentation/EmptyCommentLine:
|
|
63
|
+
Description: 'Detects empty comment lines at the start or end of documentation blocks.'
|
|
64
|
+
Enabled: true
|
|
65
|
+
Severity: convention
|
|
66
|
+
EnabledPatterns:
|
|
67
|
+
Leading: true
|
|
68
|
+
Trailing: true
|
|
69
|
+
|
|
70
|
+
Documentation/BlankLineBeforeDefinition:
|
|
71
|
+
Description: 'Detects blank lines between YARD documentation and method definition.'
|
|
72
|
+
Enabled: true
|
|
73
|
+
Severity: convention
|
|
74
|
+
OrphanedSeverity: convention
|
|
75
|
+
EnabledPatterns:
|
|
76
|
+
SingleBlankLine: true
|
|
77
|
+
OrphanedDocs: true
|
|
78
|
+
|
|
79
|
+
# Tags validators
|
|
80
|
+
Tags/Order:
|
|
81
|
+
Description: 'Enforces consistent ordering of YARD tags.'
|
|
82
|
+
Enabled: true
|
|
83
|
+
Severity: convention
|
|
84
|
+
EnforcedOrder:
|
|
85
|
+
- param
|
|
86
|
+
- option
|
|
87
|
+
- yield
|
|
88
|
+
- yieldparam
|
|
89
|
+
- yieldreturn
|
|
90
|
+
- return
|
|
91
|
+
- raise
|
|
92
|
+
- see
|
|
93
|
+
- example
|
|
94
|
+
- note
|
|
95
|
+
- todo
|
|
96
|
+
|
|
97
|
+
Tags/InvalidTypes:
|
|
98
|
+
Description: 'Validates type definitions in @param, @return, @option tags.'
|
|
99
|
+
Enabled: true
|
|
100
|
+
Severity: warning
|
|
101
|
+
ValidatedTags:
|
|
102
|
+
- param
|
|
103
|
+
- option
|
|
104
|
+
- return
|
|
105
|
+
|
|
106
|
+
Tags/TypeSyntax:
|
|
107
|
+
Description: 'Validates YARD type syntax using YARD parser.'
|
|
108
|
+
Enabled: true
|
|
109
|
+
Severity: warning
|
|
110
|
+
ValidatedTags:
|
|
111
|
+
- param
|
|
112
|
+
- option
|
|
113
|
+
- return
|
|
114
|
+
- yieldreturn
|
|
115
|
+
|
|
116
|
+
Tags/MeaninglessTag:
|
|
117
|
+
Description: 'Detects @param/@option tags on classes, modules, or constants.'
|
|
118
|
+
Enabled: true
|
|
119
|
+
Severity: warning
|
|
120
|
+
CheckedTags:
|
|
121
|
+
- param
|
|
122
|
+
- option
|
|
123
|
+
InvalidObjectTypes:
|
|
124
|
+
- class
|
|
125
|
+
- module
|
|
126
|
+
- constant
|
|
127
|
+
|
|
128
|
+
Tags/CollectionType:
|
|
129
|
+
Description: 'Validates Hash collection syntax consistency.'
|
|
130
|
+
Enabled: true
|
|
131
|
+
Severity: convention
|
|
132
|
+
EnforcedStyle: long # 'long' for Hash{K => V} (YARD standard), 'short' for {K => V}
|
|
133
|
+
ValidatedTags:
|
|
134
|
+
- param
|
|
135
|
+
- option
|
|
136
|
+
- return
|
|
137
|
+
- yieldreturn
|
|
138
|
+
|
|
139
|
+
Tags/TagTypePosition:
|
|
140
|
+
Description: 'Validates type annotation position in tags.'
|
|
141
|
+
Enabled: true
|
|
142
|
+
Severity: convention
|
|
143
|
+
CheckedTags:
|
|
144
|
+
- param
|
|
145
|
+
- option
|
|
146
|
+
# EnforcedStyle: 'type_after_name' (YARD standard: @param name [Type])
|
|
147
|
+
# or 'type_first' (@param [Type] name)
|
|
148
|
+
EnforcedStyle: type_after_name
|
|
149
|
+
|
|
150
|
+
Tags/ApiTags:
|
|
151
|
+
Description: 'Enforces @api tags on public objects.'
|
|
152
|
+
Enabled: false # Opt-in validator
|
|
153
|
+
Severity: warning
|
|
154
|
+
AllowedApis:
|
|
155
|
+
- public
|
|
156
|
+
- private
|
|
157
|
+
- internal
|
|
158
|
+
|
|
159
|
+
Tags/OptionTags:
|
|
160
|
+
Description: 'Requires @option tags for methods with options parameters.'
|
|
161
|
+
Enabled: true
|
|
162
|
+
Severity: warning
|
|
163
|
+
|
|
164
|
+
Tags/ExampleSyntax:
|
|
165
|
+
Description: 'Validates Ruby syntax in @example tags.'
|
|
166
|
+
Enabled: true
|
|
167
|
+
Severity: warning
|
|
168
|
+
|
|
169
|
+
Tags/RedundantParamDescription:
|
|
170
|
+
Description: 'Detects meaningless parameter descriptions that add no value.'
|
|
171
|
+
Enabled: true
|
|
172
|
+
Severity: convention
|
|
173
|
+
CheckedTags:
|
|
174
|
+
- param
|
|
175
|
+
- option
|
|
176
|
+
Articles:
|
|
177
|
+
- The
|
|
178
|
+
- the
|
|
179
|
+
- A
|
|
180
|
+
- a
|
|
181
|
+
- An
|
|
182
|
+
- an
|
|
183
|
+
MaxRedundantWords: 6
|
|
184
|
+
GenericTerms:
|
|
185
|
+
- object
|
|
186
|
+
- instance
|
|
187
|
+
- value
|
|
188
|
+
- data
|
|
189
|
+
- item
|
|
190
|
+
- element
|
|
191
|
+
EnabledPatterns:
|
|
192
|
+
ArticleParam: true
|
|
193
|
+
PossessiveParam: true
|
|
194
|
+
TypeRestatement: true
|
|
195
|
+
ParamToVerb: true
|
|
196
|
+
IdPattern: true
|
|
197
|
+
DirectionalDate: true
|
|
198
|
+
TypeGeneric: true
|
|
199
|
+
|
|
200
|
+
Tags/InformalNotation:
|
|
201
|
+
Description: 'Detects informal tag notation patterns like "Note:" instead of @note.'
|
|
202
|
+
Enabled: true
|
|
203
|
+
Severity: warning
|
|
204
|
+
CaseSensitive: false
|
|
205
|
+
RequireStartOfLine: true
|
|
206
|
+
Patterns:
|
|
207
|
+
Note: '@note'
|
|
208
|
+
Todo: '@todo'
|
|
209
|
+
TODO: '@todo'
|
|
210
|
+
FIXME: '@todo'
|
|
211
|
+
See: '@see'
|
|
212
|
+
See also: '@see'
|
|
213
|
+
Warning: '@deprecated'
|
|
214
|
+
Deprecated: '@deprecated'
|
|
215
|
+
Author: '@author'
|
|
216
|
+
Version: '@version'
|
|
217
|
+
Since: '@since'
|
|
218
|
+
Returns: '@return'
|
|
219
|
+
Raises: '@raise'
|
|
220
|
+
Example: '@example'
|
|
221
|
+
|
|
222
|
+
Tags/NonAsciiType:
|
|
223
|
+
Description: 'Detects non-ASCII characters in type annotations.'
|
|
224
|
+
Enabled: true
|
|
225
|
+
Severity: warning
|
|
226
|
+
ValidatedTags:
|
|
227
|
+
- param
|
|
228
|
+
- option
|
|
229
|
+
- return
|
|
230
|
+
- yieldreturn
|
|
231
|
+
- yieldparam
|
|
232
|
+
|
|
233
|
+
Tags/TagGroupSeparator:
|
|
234
|
+
Description: 'Enforces blank line separators between different YARD tag groups.'
|
|
235
|
+
Enabled: false # Opt-in validator
|
|
236
|
+
Severity: convention
|
|
237
|
+
TagGroups:
|
|
238
|
+
param: [param, option]
|
|
239
|
+
return: [return]
|
|
240
|
+
error: [raise, throws]
|
|
241
|
+
example: [example]
|
|
242
|
+
meta: [see, note, todo, deprecated, since, version, api]
|
|
243
|
+
yield: [yield, yieldparam, yieldreturn]
|
|
244
|
+
RequireAfterDescription: false
|
|
245
|
+
|
|
246
|
+
Tags/ForbiddenTags:
|
|
247
|
+
Description: 'Detects forbidden tag and type combinations.'
|
|
248
|
+
Enabled: false # Opt-in validator
|
|
249
|
+
Severity: convention
|
|
250
|
+
ForbiddenPatterns: []
|
|
251
|
+
# Example patterns:
|
|
252
|
+
# - Tag: return
|
|
253
|
+
# Types:
|
|
254
|
+
# - void
|
|
255
|
+
# - Tag: param
|
|
256
|
+
# Types:
|
|
257
|
+
# - Object
|
|
258
|
+
# - Tag: api # Forbids @api tag entirely (no Types = any occurrence)
|
|
259
|
+
|
|
260
|
+
# Warnings validators - catches YARD parser errors
|
|
261
|
+
Warnings/UnknownTag:
|
|
262
|
+
Description: 'Detects unknown YARD tags.'
|
|
263
|
+
Enabled: true
|
|
264
|
+
Severity: error
|
|
265
|
+
|
|
266
|
+
Warnings/UnknownDirective:
|
|
267
|
+
Description: 'Detects unknown YARD directives.'
|
|
268
|
+
Enabled: true
|
|
269
|
+
Severity: error
|
|
270
|
+
|
|
271
|
+
Warnings/InvalidTagFormat:
|
|
272
|
+
Description: 'Detects malformed tag syntax.'
|
|
273
|
+
Enabled: true
|
|
274
|
+
Severity: error
|
|
275
|
+
|
|
276
|
+
Warnings/InvalidDirectiveFormat:
|
|
277
|
+
Description: 'Detects malformed directive syntax.'
|
|
278
|
+
Enabled: true
|
|
279
|
+
Severity: error
|
|
280
|
+
|
|
281
|
+
Warnings/DuplicatedParameterName:
|
|
282
|
+
Description: 'Detects duplicate @param tags.'
|
|
283
|
+
Enabled: true
|
|
284
|
+
Severity: error
|
|
285
|
+
|
|
286
|
+
Warnings/UnknownParameterName:
|
|
287
|
+
Description: 'Detects @param tags for non-existent parameters.'
|
|
288
|
+
Enabled: true
|
|
289
|
+
Severity: error
|
|
290
|
+
|
|
291
|
+
# Semantic validators
|
|
292
|
+
Semantic/AbstractMethods:
|
|
293
|
+
Description: 'Ensures @abstract methods do not have real implementations.'
|
|
294
|
+
Enabled: true
|
|
295
|
+
Severity: warning
|
data/AGENTS.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
You are working in a Ruby project that uses mutation testing.
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
Achieve 100% mutation coverage. Verify with:
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
bundle exec mutant run
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
When iterating, prefer `--fail-fast` so you address one surviving
|
|
12
|
+
mutant at a time:
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
bundle exec mutant run --fail-fast
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## When you find an alive mutation
|
|
19
|
+
|
|
20
|
+
Decide which bucket it falls into:
|
|
21
|
+
|
|
22
|
+
- **A) The code does too much** for what the tests ask for. The
|
|
23
|
+
surviving mutation reveals behavior that no test requires. The
|
|
24
|
+
fix is to simplify the implementation.
|
|
25
|
+
- **B) A test is missing.** The behavior is intentional but no test
|
|
26
|
+
observes it. The fix is to add a test.
|
|
27
|
+
|
|
28
|
+
Decide between A) and B) before changing anything. If unsure, ask
|
|
29
|
+
the user.
|
|
30
|
+
|
|
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
|
+
## Constraints
|
|
38
|
+
|
|
39
|
+
- Line coverage must stay at 100%. Verify with:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
SIMPLECOV=1 bundle exec rspec
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
- You may not skip mutants by configuring mutant to ignore them.
|
|
46
|
+
No `expressions:` filters, no `coverage_criteria:` tweaks.
|
|
47
|
+
- You may not use `send` or `__send__` to invoke private methods
|
|
48
|
+
in tests just to satisfy mutant.
|
|
49
|
+
- You may not stub or mock the system under test (`Age`).
|
|
50
|
+
|
|
51
|
+
## Done
|
|
52
|
+
|
|
53
|
+
You are done when both of these are green:
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
SIMPLECOV=1 bundle exec rspec
|
|
57
|
+
bundle exec mutant run
|
|
58
|
+
```
|
data/Appraisals
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -5,8 +5,23 @@ This format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
|
|
|
5
5
|
|
|
6
6
|
## Unreleased
|
|
7
7
|
|
|
8
|
+
## 0.7.0
|
|
9
|
+
## Added
|
|
10
|
+
- Codebase is fully mutant tested now and test coverage increased
|
|
11
|
+
|
|
12
|
+
## Changed
|
|
13
|
+
- Codebase was simplified according to mutants
|
|
14
|
+
|
|
15
|
+
## 0.6.0
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- ProcessingJob to discard job and report an error on DeserializationError
|
|
19
|
+
- ProcessingJob to handle nil or wrong class argument gracefully
|
|
20
|
+
- Ensure that ID is always generated for ReceivedWebhook model
|
|
21
|
+
|
|
8
22
|
## 0.5.0
|
|
9
23
|
|
|
24
|
+
### Changed
|
|
10
25
|
- Forked from [munster](https://github.com/cheddar-me/munster) and renamed to webhukhs
|
|
11
26
|
- Changed GitHub repository to https://github.com/skatkov/webhukhs
|
|
12
27
|
- Testing against rails 7.1, rails 8.0 and rails 8.1
|
data/README.md
CHANGED
|
@@ -25,17 +25,19 @@ Mount webhukhs engine in your routes.
|
|
|
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`.
|
|
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:
|
|
29
29
|
|
|
30
30
|
```ruby
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
Webhukhs.configure do |config|
|
|
32
|
+
config.active_handlers = {
|
|
33
|
+
"test-handler" => "WebhookTestHandler"
|
|
34
|
+
}
|
|
35
|
+
end
|
|
34
36
|
```
|
|
35
37
|
|
|
36
|
-
##
|
|
37
|
-
|
|
38
|
-
We provide a number of webhook handlers which demonstrate certain features of Webhukhs. You will find them in `handler-examples`.
|
|
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`.
|
|
39
41
|
|
|
40
42
|
## Requirements
|
|
41
43
|
|
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
|
|
@@ -2,18 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "jobs/processing_job"
|
|
4
4
|
|
|
5
|
+
# Namespace for webhook ingestion and processing classes.
|
|
5
6
|
module Webhukhs
|
|
7
|
+
# Base class for webhook handlers.
|
|
6
8
|
class BaseHandler
|
|
7
9
|
# `handle` accepts the ActionDispatch HTTP request and saves the webhook for later processing. It then
|
|
8
10
|
# enqueues an ActiveJob which will perform the processing using `process`.
|
|
9
11
|
#
|
|
10
|
-
# @param action_dispatch_request[ActionDispatch::Request]
|
|
12
|
+
# @param action_dispatch_request [ActionDispatch::Request] request from the controller
|
|
11
13
|
# @return [void]
|
|
12
14
|
def handle(action_dispatch_request)
|
|
13
|
-
handler_module_name =
|
|
15
|
+
handler_module_name = self.class.name
|
|
14
16
|
handler_event_id = extract_event_id_from_request(action_dispatch_request)
|
|
15
17
|
|
|
16
|
-
webhook =
|
|
18
|
+
webhook = ReceivedWebhook.new(request: action_dispatch_request, handler_event_id: handler_event_id, handler_module_name: handler_module_name)
|
|
17
19
|
webhook.save!
|
|
18
20
|
|
|
19
21
|
enqueue(webhook)
|
|
@@ -42,7 +44,7 @@ module Webhukhs
|
|
|
42
44
|
# body of the webhook request, but also the full (as-full-as-possible) clone of the original ActionDispatch::Request
|
|
43
45
|
# that you can use.
|
|
44
46
|
#
|
|
45
|
-
# @param received_webhook[Webhukhs::ReceivedWebhook]
|
|
47
|
+
# @param received_webhook [Webhukhs::ReceivedWebhook] persisted webhook to process
|
|
46
48
|
# @return [void]
|
|
47
49
|
def process(received_webhook)
|
|
48
50
|
end
|
|
@@ -60,9 +62,9 @@ module Webhukhs
|
|
|
60
62
|
# If this method returns `false`, the webhook will be marked as `failed_validation` in the database. If this
|
|
61
63
|
# method returns `true`, the `process` method of the handler is going to be called.
|
|
62
64
|
#
|
|
63
|
-
# @
|
|
64
|
-
# @param action_dispatch_request[ActionDispatch::Request] the reconstructed request from the controller
|
|
65
|
+
# @param action_dispatch_request [ActionDispatch::Request] reconstructed request from the stored webhook
|
|
65
66
|
# @return [Boolean]
|
|
67
|
+
# @see Webhukhs::ReceivedWebhook#request
|
|
66
68
|
def valid?(action_dispatch_request)
|
|
67
69
|
true
|
|
68
70
|
end
|
|
@@ -71,6 +73,7 @@ module Webhukhs
|
|
|
71
73
|
# an event ID we use it for deduplication. A duplicate webhook is not going to be
|
|
72
74
|
# stored in the database if it is already present there.
|
|
73
75
|
#
|
|
76
|
+
# @param action_dispatch_request [ActionDispatch::Request] request used to derive a stable event identifier
|
|
74
77
|
# @return [String]
|
|
75
78
|
def extract_event_id_from_request(action_dispatch_request)
|
|
76
79
|
SecureRandom.uuid
|
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Webhukhs
|
|
4
|
+
# Receives incoming webhook HTTP requests and dispatches handlers.
|
|
4
5
|
class ReceiveWebhooksController < ActionController::API
|
|
6
|
+
# Raised when a configured handler exists but is currently inactive.
|
|
5
7
|
class HandlerInactive < StandardError
|
|
6
8
|
end
|
|
7
9
|
|
|
10
|
+
# Raised when no handler is configured for the service id.
|
|
8
11
|
class UnknownHandler < StandardError
|
|
9
12
|
end
|
|
10
13
|
|
|
14
|
+
# Persists and schedules processing for an incoming webhook request.
|
|
15
|
+
#
|
|
16
|
+
# @return [void]
|
|
11
17
|
def create
|
|
12
18
|
Rails.error.set_context(**Webhukhs.configuration.error_context)
|
|
13
19
|
handler = lookup_handler(service_id)
|
|
@@ -15,27 +21,40 @@ module Webhukhs
|
|
|
15
21
|
handler.handle(request)
|
|
16
22
|
render(json: {ok: true, error: nil})
|
|
17
23
|
rescue UnknownHandler => e
|
|
18
|
-
Rails.error.report(e,
|
|
24
|
+
Rails.error.report(e, severity: :error)
|
|
19
25
|
render_error_with_status("No handler found for #{service_id.inspect}", status: :not_found)
|
|
20
26
|
rescue HandlerInactive => e
|
|
21
|
-
Rails.error.report(e,
|
|
27
|
+
Rails.error.report(e, severity: :error)
|
|
22
28
|
render_error_with_status("Webhook handler #{service_id.inspect} is inactive", status: :service_unavailable)
|
|
23
29
|
rescue => e
|
|
24
|
-
raise
|
|
25
|
-
raise
|
|
26
|
-
Rails.error.report(e,
|
|
30
|
+
raise unless handler
|
|
31
|
+
raise if handler.expose_errors_to_sender?
|
|
32
|
+
Rails.error.report(e, severity: :error)
|
|
27
33
|
render_error_with_status("Internal error (#{e})")
|
|
28
34
|
end
|
|
29
35
|
|
|
36
|
+
# Extracts webhook service identifier from route parameters.
|
|
37
|
+
#
|
|
38
|
+
# @return [String]
|
|
30
39
|
def service_id
|
|
31
40
|
params.require(:service_id)
|
|
32
41
|
end
|
|
33
42
|
|
|
34
|
-
|
|
35
|
-
|
|
43
|
+
# Renders standardized error response payload.
|
|
44
|
+
#
|
|
45
|
+
# @param message_str [String] human-readable error message
|
|
46
|
+
# @param status [Symbol] HTTP response status
|
|
47
|
+
# @return [void]
|
|
48
|
+
def render_error_with_status(message_str, status: nil)
|
|
49
|
+
json = {ok: false, error: message_str}
|
|
36
50
|
render(json: json, status: status)
|
|
37
51
|
end
|
|
38
52
|
|
|
53
|
+
# Resolves a handler class from configuration for a service id.
|
|
54
|
+
#
|
|
55
|
+
# @param service_id_str [String] service identifier from request path
|
|
56
|
+
# @return [Webhukhs::BaseHandler]
|
|
57
|
+
# @raise [UnknownHandler] when no active handler is configured for service id
|
|
39
58
|
def lookup_handler(service_id_str)
|
|
40
59
|
active_handlers = Webhukhs.configuration.active_handlers.with_indifferent_access
|
|
41
60
|
# The config can specify a mapping of:
|
data/lib/webhukhs/engine.rb
CHANGED
|
@@ -4,26 +4,33 @@ require "rails/generators"
|
|
|
4
4
|
require "rails/generators/active_record"
|
|
5
5
|
|
|
6
6
|
module Webhukhs
|
|
7
|
-
#
|
|
8
7
|
# Rails generator used for setting up Webhukhs in a Rails application.
|
|
9
8
|
# Run it with +bin/rails g webhukhs:install+ in your console.
|
|
10
|
-
#
|
|
11
9
|
class InstallGenerator < Rails::Generators::Base
|
|
12
10
|
include ActiveRecord::Generators::Migration
|
|
13
11
|
|
|
14
12
|
source_root File.expand_path("../templates", __FILE__)
|
|
15
13
|
|
|
14
|
+
# Creates migration files required by Webhukhs tables.
|
|
15
|
+
#
|
|
16
|
+
# @return [void]
|
|
16
17
|
def create_migration_file
|
|
17
18
|
migration_template "create_webhukhs_tables.rb.erb", File.join(db_migrate_path, "create_webhukhs_tables.rb")
|
|
18
19
|
migration_template "add_headers_to_webhukhs_webhooks.rb.erb", File.join(db_migrate_path, "add_headers_to_webhukhs_webhooks.rb")
|
|
19
20
|
end
|
|
20
21
|
|
|
22
|
+
# Copies initializer file into host application.
|
|
23
|
+
#
|
|
24
|
+
# @return [void]
|
|
21
25
|
def copy_files
|
|
22
26
|
template "webhukhs.rb", File.join("config", "initializers", "webhukhs.rb")
|
|
23
27
|
end
|
|
24
28
|
|
|
25
29
|
private
|
|
26
30
|
|
|
31
|
+
# Declares ActiveRecord migration version.
|
|
32
|
+
#
|
|
33
|
+
# @return [String]
|
|
27
34
|
def migration_version
|
|
28
35
|
"[#{ActiveRecord::VERSION::STRING.to_f}]"
|
|
29
36
|
end
|