glib-web 5.0.5 → 5.0.7
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
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6885a1b150291881895f5b3cdfda3c2424961e5fd953e7faebd2d421242d930c
|
|
4
|
+
data.tar.gz: 2c9b74ee8ac40b6b887d3b14a06915b98b0562a207d578d63b955a12b7e235f9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 67e1d34169a5cea5970378da912372ff6b0c5b1e0b7793c9e05fc50347cd1e118f55d0b9b63a328a60fdff810c913ba94575198eee0cd147d161feb8272c6c27
|
|
7
|
+
data.tar.gz: dadf602d4b72f9c2c2ef4434182c94eb6690f05e1ec934226de01d8c24cef88719f29cba394d12c5e13df93e82144f46ae8425443b4318807780bcde5381d761
|
|
@@ -38,7 +38,16 @@ module Glib::Analytics
|
|
|
38
38
|
referrer_url = request.headers['referer'] # Notice that the HTTP header uses one "r"
|
|
39
39
|
if group.nil? && action.nil? && !referrer_url.nil?
|
|
40
40
|
current_host = request.host
|
|
41
|
-
|
|
41
|
+
# The Referer header is attacker-controlled and may not be a valid URI
|
|
42
|
+
# (scanners send junk like "() { ignored; }; cat /etc/passwd"). Treat an
|
|
43
|
+
# unparseable referer as "no referer" rather than letting URI::InvalidURIError
|
|
44
|
+
# bubble up mid-render, where the 500 rescue then double-renders.
|
|
45
|
+
referrer_host =
|
|
46
|
+
begin
|
|
47
|
+
URI.parse(referrer_url).host
|
|
48
|
+
rescue URI::InvalidURIError
|
|
49
|
+
nil
|
|
50
|
+
end
|
|
42
51
|
|
|
43
52
|
# Replace the subdomain portion with regex so it will only match the non-subdomain part.
|
|
44
53
|
# This will allow cross-subdomain referral, but it will not work if the host is a bare domain,
|
|
@@ -249,6 +249,13 @@ module Glib::Json::Libs
|
|
|
249
249
|
Rollbar.error(exception, use_exception_level_filters: true)
|
|
250
250
|
end
|
|
251
251
|
|
|
252
|
+
# The exception may have been raised AFTER a response was already rendered
|
|
253
|
+
# (e.g. from an after_action injection). Rendering the error page on top of that
|
|
254
|
+
# would raise AbstractController::DoubleRenderError, which masks the genuine
|
|
255
|
+
# exception reported above. The real cause is already logged, so leave the
|
|
256
|
+
# already-rendered response in place rather than double-rendering.
|
|
257
|
+
return if performed?
|
|
258
|
+
|
|
252
259
|
render file: Rails.root.join('public', '500.html'), status: :internal_server_error
|
|
253
260
|
else
|
|
254
261
|
raise exception
|
|
@@ -261,6 +268,12 @@ module Glib::Json::Libs
|
|
|
261
268
|
def glib_json_handle_404(exception, preview_mode)
|
|
262
269
|
if json_ui_activated?
|
|
263
270
|
if Rails.env.production? || preview_mode
|
|
271
|
+
# As with glib_json_handle_500: if a response was already rendered (e.g. an
|
|
272
|
+
# after_action raised RecordNotFound), rendering the 404 page on top would raise
|
|
273
|
+
# AbstractController::DoubleRenderError and mask the real exception. Keep the
|
|
274
|
+
# already-rendered response instead of double-rendering.
|
|
275
|
+
return if performed?
|
|
276
|
+
|
|
264
277
|
render file: Rails.root.join('public', '404.html'), status: :not_found
|
|
265
278
|
else
|
|
266
279
|
raise exception
|
data/lib/glib/mailer_tester.rb
CHANGED
|
@@ -8,11 +8,7 @@ module Glib
|
|
|
8
8
|
|
|
9
9
|
module ClassMethods
|
|
10
10
|
def generate_preview_tests
|
|
11
|
-
|
|
12
|
-
paths = Dir.glob(project_root + 'test/mailers/previews/*')
|
|
13
|
-
paths.each do |file|
|
|
14
|
-
require file
|
|
15
|
-
end
|
|
11
|
+
Glib::MailerTester.load_preview_files
|
|
16
12
|
|
|
17
13
|
ActionMailer::Preview.subclasses.each do |preview_class|
|
|
18
14
|
preview = preview_class.new
|
|
@@ -45,6 +41,95 @@ module Glib
|
|
|
45
41
|
end
|
|
46
42
|
end
|
|
47
43
|
end
|
|
44
|
+
|
|
45
|
+
# Opt-in companion to `generate_preview_tests`. That method only snapshot-tests
|
|
46
|
+
# the previews that EXIST; this asserts every mailer ACTION has a matching
|
|
47
|
+
# preview, so a new mailer/action can't silently ship with no preview.
|
|
48
|
+
#
|
|
49
|
+
# `except:` allowlists "Mailer#action" strings that intentionally have no
|
|
50
|
+
# preview — document the reason at the call site.
|
|
51
|
+
def assert_all_mailer_actions_have_previews(except: [])
|
|
52
|
+
test 'every mailer action has a preview' do
|
|
53
|
+
missing = Glib::MailerTester.missing_previews(except: except)
|
|
54
|
+
assert missing.empty?, Glib::MailerTester.missing_previews_message(missing)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# --- Preview coverage (companion to generate_preview_tests) ---------------
|
|
60
|
+
|
|
61
|
+
# Loads every preview file (idempotent — `require` no-ops if already loaded).
|
|
62
|
+
def self.load_preview_files
|
|
63
|
+
Dir.glob(Rails.root + 'test/mailers/previews/*').each { |file| require file }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Sorted Array<String> of "Mailer#action" entries that have no preview.
|
|
67
|
+
def self.missing_previews(except: [])
|
|
68
|
+
load_preview_files
|
|
69
|
+
compute_missing_previews(
|
|
70
|
+
mailer_actions: reflect_mailer_actions,
|
|
71
|
+
previewed_actions: ->(mailer) { reflect_preview_methods(mailer) },
|
|
72
|
+
except: except
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Pure (no Rails reflection) so it is unit-testable.
|
|
77
|
+
# mailer_actions: { "FooMailer" => ["welcome", "goodbye"] }
|
|
78
|
+
# previewed_actions: ->(mailer_name) { ["welcome"] }
|
|
79
|
+
# except: ["FooMailer#goodbye"]
|
|
80
|
+
def self.compute_missing_previews(mailer_actions:, previewed_actions:, except: [])
|
|
81
|
+
excluded = except.map(&:to_s).to_set
|
|
82
|
+
mailer_actions.flat_map do |mailer, actions|
|
|
83
|
+
previewed = Array(previewed_actions.call(mailer)).map(&:to_s).to_set
|
|
84
|
+
actions.map(&:to_s)
|
|
85
|
+
.reject { |action| previewed.include?(action) }
|
|
86
|
+
.map { |action| "#{mailer}##{action}" }
|
|
87
|
+
end.reject { |id| excluded.include?(id) }.sort
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def self.missing_previews_message(missing)
|
|
91
|
+
<<~MSG
|
|
92
|
+
Every mailer action must have a preview so `generate_preview_tests` snapshot-tests it.
|
|
93
|
+
These actions have none:
|
|
94
|
+
|
|
95
|
+
#{missing.join("\n ")}
|
|
96
|
+
|
|
97
|
+
Add a method named after the action to the matching `<Mailer>Preview` class under
|
|
98
|
+
test/mailers/previews/, returning `<Mailer>.<action>(...)`. If an action genuinely
|
|
99
|
+
cannot be previewed, allowlist it with a reason:
|
|
100
|
+
|
|
101
|
+
assert_all_mailer_actions_have_previews(except: ['FooMailer#some_action']) # why
|
|
102
|
+
MSG
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Reflection glue (needs a loaded Rails app). Scopes to app mailers under
|
|
106
|
+
# `ApplicationMailer` so third-party mailers (Devise, etc.) are not flagged.
|
|
107
|
+
def self.reflect_mailer_actions
|
|
108
|
+
load_mailer_files
|
|
109
|
+
base = '::ApplicationMailer'.safe_constantize || ActionMailer::Base
|
|
110
|
+
base.descendants.each_with_object({}) do |mailer, acc|
|
|
111
|
+
actions = mailer.action_methods.to_a
|
|
112
|
+
acc[mailer.name] = actions unless actions.empty?
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Loads only `app/mailers` (so every mailer subclass is defined) rather than
|
|
117
|
+
# eager-loading the whole app — faster, and avoids unrelated load failures.
|
|
118
|
+
def self.load_mailer_files
|
|
119
|
+
dir = Rails.root.join('app/mailers')
|
|
120
|
+
return unless dir.exist?
|
|
121
|
+
|
|
122
|
+
loader = Rails.respond_to?(:autoloaders) ? Rails.autoloaders.main : nil
|
|
123
|
+
if loader.respond_to?(:eager_load_dir)
|
|
124
|
+
loader.eager_load_dir(dir.to_s)
|
|
125
|
+
else
|
|
126
|
+
Rails.application.eager_load!
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def self.reflect_preview_methods(mailer_name)
|
|
131
|
+
preview = "#{mailer_name}Preview".safe_constantize
|
|
132
|
+
preview ? preview.instance_methods(false).map(&:to_s) : []
|
|
48
133
|
end
|
|
49
134
|
|
|
50
135
|
def log_root_dir
|
data/lib/glib/test_helpers.rb
CHANGED
|
@@ -134,8 +134,7 @@ module Glib
|
|
|
134
134
|
# submit_form(response.body, { chat: { message_body: 'Hello' } })
|
|
135
135
|
#
|
|
136
136
|
def submit_form(page_payload, data, url: nil, method: nil)
|
|
137
|
-
|
|
138
|
-
form = __find_form(json)
|
|
137
|
+
form = find_form(page_payload)
|
|
139
138
|
|
|
140
139
|
raise 'No form found in page. Ensure the form is rendered properly.' unless form
|
|
141
140
|
|
|
@@ -173,6 +172,23 @@ module Glib
|
|
|
173
172
|
assert_response :success
|
|
174
173
|
end
|
|
175
174
|
|
|
175
|
+
# Locates the form in a rendered page payload without submitting it -- useful for
|
|
176
|
+
# asserting on a form's attributes (e.g. its HTTP method) in a test. Returns the form
|
|
177
|
+
# Hash, or nil when the page renders no form. When several forms are present it returns
|
|
178
|
+
# the one with the most childViews, the same selection submit_form uses internally.
|
|
179
|
+
#
|
|
180
|
+
# @param page_payload [String, Hash] The JSON response body, or an already-parsed Hash
|
|
181
|
+
# @return [Hash, nil] The located form view, or nil if none is rendered
|
|
182
|
+
#
|
|
183
|
+
# @example
|
|
184
|
+
# get new_chat_url(checklist_id: checklist.id), params: json_params
|
|
185
|
+
# assert_equal 'post', find_form(response.body)['method']
|
|
186
|
+
#
|
|
187
|
+
def find_form(page_payload)
|
|
188
|
+
json = page_payload.is_a?(String) ? JSON.parse(page_payload) : page_payload
|
|
189
|
+
__find_form(json)
|
|
190
|
+
end
|
|
191
|
+
|
|
176
192
|
def glib_travel(*args, &block)
|
|
177
193
|
Timecop.travel(*args, &block)
|
|
178
194
|
end
|
|
@@ -207,6 +223,11 @@ module Glib
|
|
|
207
223
|
|
|
208
224
|
def __build_form_params(form, data)
|
|
209
225
|
params = {}
|
|
226
|
+
|
|
227
|
+
# Skip ALL hidden fields under the same model prefix, not just exact key matches.
|
|
228
|
+
# Hidden fields use flat bracket notation (e.g. "submission[task_id]") while data uses
|
|
229
|
+
# nested hashes (e.g. { submission: { signature_submission_attributes: { ... } } }).
|
|
230
|
+
# Mixing both in params makes Rails misparse them, resulting in 403 Forbidden errors.
|
|
210
231
|
data_prefixes = data.keys.map { |k| "#{k}[" }
|
|
211
232
|
|
|
212
233
|
__extract_form_fields(form).each do |field|
|