glib-web 5.0.9 → 5.1.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.
- checksums.yaml +4 -4
- data/app/models/concerns/glib/enum_symbolization.rb +65 -11
- data/lib/glib/last_resort_unit_test.rb +28 -0
- data/lib/glib/non_http_integration_test.rb +27 -0
- data/lib/glib/test/changed_files_rubocop.rb +55 -0
- data/lib/glib/test_helpers.rb +17 -1
- data/lib/glib-web.rb +2 -1
- metadata +10 -3
- data/lib/glib/version.rb +0 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 783a7ad2cc1fcb744c02e91df3185c7d455c6bef84d56fc0eff2503f3a39f826
|
|
4
|
+
data.tar.gz: 960797098836ddd26a310b592f953f46284f986bde09f42deae1b889a5a85cd1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e5a23a583b69403fd8a65db92b658fb2aab0b7ca6e2946ba4da0e39d194504b2ed9694c638167d68828b92c97d7c3851746eb189be75576ec777201e8ad570a1
|
|
7
|
+
data.tar.gz: 43ac55043ae8321c33aa4c662b5f4833be211cf0d95f6cc9f8ad5b16f4b7685a4a475e5e0c77d3c01c99292f1401577a8b600f2e863055274582c1a5d0997bfb
|
|
@@ -1,26 +1,80 @@
|
|
|
1
1
|
module Glib
|
|
2
|
-
# Class-level helper
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
# holding (see backend/01a_defensive_programming.md item 7).
|
|
2
|
+
# Class-level helper for declaring enums that read back as symbols, can never
|
|
3
|
+
# be persisted nil, and — crucially — know their attribute type even before
|
|
4
|
+
# the backing column exists.
|
|
6
5
|
#
|
|
7
|
-
#
|
|
6
|
+
# Preferred form (declares everything in one call; use INSTEAD of a bare
|
|
7
|
+
# `enum`). Because it calls `attribute` before `enum`, the enum's type comes
|
|
8
|
+
# from the declared values rather than from column introspection, so a model
|
|
9
|
+
# loaded against a not-yet-migrated column (fresh deploy, eager-loaded CI, an
|
|
10
|
+
# un-migrated local DB) no longer raises "Undeclared attribute type for enum":
|
|
8
11
|
#
|
|
9
12
|
# class Order < ApplicationRecord
|
|
10
|
-
#
|
|
11
|
-
# enum :finalize_intent, { unknown: 0, charge: 1 }, prefix: true
|
|
13
|
+
# include Glib::EnumSymbolization
|
|
12
14
|
#
|
|
13
|
-
# enum_symbolize :payment_status, :
|
|
15
|
+
# enum_symbolize :payment_status, { draft: 0, pending: 1, finalized: 2 }
|
|
16
|
+
# enum_symbolize :finalize_intent, { unknown: 0, charge: 1 }, prefix: true
|
|
14
17
|
# end
|
|
18
|
+
#
|
|
19
|
+
# The attribute type is inferred from the values: string values back a
|
|
20
|
+
# :string column, everything else (integer-keyed hashes, positional arrays)
|
|
21
|
+
# backs an :integer column. Pass the values as a *braced* hash — a bare
|
|
22
|
+
# `key: 0` list is parsed as keyword options, not values.
|
|
23
|
+
#
|
|
24
|
+
# Legacy form (attach presence + symbolized reader to enum(s) already declared
|
|
25
|
+
# above). Prefer the form above for new code:
|
|
26
|
+
#
|
|
27
|
+
# enum :status, { draft: 0, published: 1 }
|
|
28
|
+
# enum_symbolize :status
|
|
29
|
+
#
|
|
30
|
+
# Both forms add `validates :attr, presence: true` (a nil enum has no symbolic
|
|
31
|
+
# key and is almost always a bug) and override the reader to return a symbol,
|
|
32
|
+
# so callers can compare with `record.status == :active` without tracking
|
|
33
|
+
# which form they're holding (see backend/01a_defensive_programming.md item 7).
|
|
15
34
|
module EnumSymbolization
|
|
16
35
|
extend ActiveSupport::Concern
|
|
17
36
|
|
|
18
37
|
class_methods do
|
|
19
|
-
def enum_symbolize(*attrs)
|
|
20
|
-
attrs.
|
|
21
|
-
|
|
38
|
+
def enum_symbolize(*attrs, **opts)
|
|
39
|
+
if attrs.size >= 2 && (attrs[1].is_a?(Hash) || attrs[1].is_a?(Array))
|
|
40
|
+
# Full form: enum_symbolize :name, { ... }[, **enum_opts]
|
|
41
|
+
name, values = attrs
|
|
42
|
+
# MUST precede `enum`, and is NOT redundant: `enum` resolves its type
|
|
43
|
+
# by introspecting the DB column, which raises "Undeclared attribute
|
|
44
|
+
# type for enum" (RuntimeError) when the column isn't there yet — a
|
|
45
|
+
# not-yet-migrated column loaded on deploy, eager-loaded CI, or an
|
|
46
|
+
# un-migrated local DB. Declaring the attribute first pins the type
|
|
47
|
+
# from the enum's own values, so it no longer needs the column. Do not
|
|
48
|
+
# remove or move below `enum`.
|
|
49
|
+
attribute name, Glib::EnumSymbolization.backing_type(values)
|
|
50
|
+
enum name, values, **opts
|
|
51
|
+
glib_symbolize_reader(name)
|
|
52
|
+
else
|
|
53
|
+
# Legacy form: enum_symbolize :a[, :b, ...] for enums already declared.
|
|
54
|
+
if opts.any?
|
|
55
|
+
raise ArgumentError,
|
|
56
|
+
"enum_symbolize: pass enum values as a braced hash, " \
|
|
57
|
+
"e.g. enum_symbolize :#{attrs.first}, { ... }"
|
|
58
|
+
end
|
|
59
|
+
attrs.each { |attr| glib_symbolize_reader(attr) }
|
|
22
60
|
end
|
|
23
61
|
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def glib_symbolize_reader(attr)
|
|
66
|
+
validates attr, presence: true
|
|
67
|
+
define_method(attr) { super()&.to_sym }
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Infer the attribute type backing an enum from its declared values, so the
|
|
72
|
+
# type never depends on the (possibly not-yet-migrated) DB column. A hash
|
|
73
|
+
# whose values are strings backs a :string column; integer-keyed hashes and
|
|
74
|
+
# positional arrays back an :integer column.
|
|
75
|
+
def self.backing_type(values)
|
|
76
|
+
first = values.is_a?(Hash) ? values.values.first : nil
|
|
77
|
+
first.is_a?(String) ? :string : :integer
|
|
24
78
|
end
|
|
25
79
|
end
|
|
26
80
|
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module Glib
|
|
2
|
+
# The truthful base class for the rare, justified unit tests (see dev-doc's
|
|
3
|
+
# AvoidUnitTest / RequireUnitTestJustification). Every other test should be a
|
|
4
|
+
# controller test on Glib::IntegrationTest.
|
|
5
|
+
#
|
|
6
|
+
# The name is the policy: you may only be here after exhausting the
|
|
7
|
+
# controller-test route. Assume a controller test IS possible and look
|
|
8
|
+
# harder first — that conclusion is almost always premature (AvoidUnitTest's
|
|
9
|
+
# docs list the patterns that make "unit-only" behaviour reachable
|
|
10
|
+
# end-to-end). Subclassing this base is a confession, not a credential: it
|
|
11
|
+
# must sit in a unit-test directory under a justification header, both
|
|
12
|
+
# enforced by RequireUnitTestJustification.
|
|
13
|
+
#
|
|
14
|
+
# Deliberately NOT a descendant of ActionDispatch::IntegrationTest:
|
|
15
|
+
#
|
|
16
|
+
# - The declaration line states the test type honestly. A unit test
|
|
17
|
+
# subclassing an "IntegrationTest" base is a lie that also defeats
|
|
18
|
+
# base-class-keyed tooling.
|
|
19
|
+
# - HTTP verb helpers (`get`/`post`/...) don't exist here, so a unit test
|
|
20
|
+
# structurally cannot drift into half-integration territory — the inverse
|
|
21
|
+
# of the guarantee DevDoc::Test::Lints::HttpDrivenControllerTests provides
|
|
22
|
+
# for test/controllers/.
|
|
23
|
+
#
|
|
24
|
+
# Project-wide helpers attached to ActiveSupport::TestCase (fixtures,
|
|
25
|
+
# glib_travel, coverage) are inherited as usual.
|
|
26
|
+
class LastResortUnitTest < ActiveSupport::TestCase
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require 'active_job/test_helper'
|
|
2
|
+
|
|
3
|
+
module Glib
|
|
4
|
+
# The truthful base for full-stack side-effect tests (jobs, mailers,
|
|
5
|
+
# channels): real DB, fixtures, deliveries and enqueued-job execution — but
|
|
6
|
+
# no HTTP verbs. Use it when the contract under test is a side effect (a
|
|
7
|
+
# job's record mutations, a mailer's body and links) rather than an HTTP
|
|
8
|
+
# response; tests that DO assert responses belong on Glib::IntegrationTest.
|
|
9
|
+
#
|
|
10
|
+
# Deliberately NOT a descendant of ActionDispatch::IntegrationTest, for the
|
|
11
|
+
# same reasons as Glib::LastResortUnitTest: the declaration line states the
|
|
12
|
+
# test type honestly, and the HTTP verb helpers are structurally absent, so
|
|
13
|
+
# a side-effect test cannot silently drift into half-integration territory.
|
|
14
|
+
# Unlike LastResortUnitTest this base is not a confession — jobs and mailers
|
|
15
|
+
# genuinely have no HTTP surface of their own — but it is confined all the
|
|
16
|
+
# same: DevDoc/Test/RequireUnitTestJustification only permits it inside the
|
|
17
|
+
# side-effect directories (test/jobs/, test/mailers/, test/channels/),
|
|
18
|
+
# because anywhere else it would evade that directory's enforcement.
|
|
19
|
+
#
|
|
20
|
+
# Project-wide helpers attached to ActiveSupport::TestCase (fixtures,
|
|
21
|
+
# glib_travel, coverage) are inherited as usual; ActiveJob::TestHelper is
|
|
22
|
+
# included here because running the very jobs under test
|
|
23
|
+
# (perform_enqueued_jobs) is this tier's bread and butter.
|
|
24
|
+
class NonHttpIntegrationTest < ActiveSupport::TestCase
|
|
25
|
+
include ActiveJob::TestHelper
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'English'
|
|
4
|
+
require 'shellwords'
|
|
5
|
+
|
|
6
|
+
module Glib
|
|
7
|
+
module Test
|
|
8
|
+
# Fast RuboCop guard for the subset-run workflow.
|
|
9
|
+
#
|
|
10
|
+
# Lives in glib-web's test helper so any project that includes
|
|
11
|
+
# `Glib::TestHelpers` into `ActiveSupport::TestCase` picks it up. Minitest
|
|
12
|
+
# runs all loaded test classes on every invocation, so this fires on every
|
|
13
|
+
# `rails test` — but it lints ONLY the files you have changed in the
|
|
14
|
+
# working tree (unstaged + staged + untracked). That keeps it fast and
|
|
15
|
+
# fails the run the moment an edited file has an offense.
|
|
16
|
+
#
|
|
17
|
+
# Skip with `SKIP_CHANGED_RUBOCOP=1` during tight debug loops. Auto-skips
|
|
18
|
+
# on CI because gem caches put thousands of vendored `.rb` files into the
|
|
19
|
+
# working tree (the "working-tree diff == your edits" premise breaks).
|
|
20
|
+
class ChangedFilesRubocop < ::ActiveSupport::TestCase
|
|
21
|
+
# Extensions RuboCop targets by default (see its AllCops/Include). We
|
|
22
|
+
# rely on `--force-exclusion` to honour the project's AllCops/Exclude.
|
|
23
|
+
RUBOCOP_EXTENSIONS = %w[.rb .rake .jbuilder .gemspec].freeze
|
|
24
|
+
|
|
25
|
+
def test_changed_files_are_rubocop_clean
|
|
26
|
+
skip 'opted out via SKIP_CHANGED_RUBOCOP' if ENV['SKIP_CHANGED_RUBOCOP'] == '1'
|
|
27
|
+
skip 'CI lints every file separately; this guard is for local working-tree edits' if ENV['CI']
|
|
28
|
+
|
|
29
|
+
files = changed_ruby_files
|
|
30
|
+
# IMPORTANT: never call rubocop with zero paths — it would lint the whole repo.
|
|
31
|
+
skip 'no changed Ruby files in the working tree' if files.empty?
|
|
32
|
+
|
|
33
|
+
output = `bundle exec rubocop --force-exclusion --format simple #{files.shelljoin} 2>&1`
|
|
34
|
+
|
|
35
|
+
assert(
|
|
36
|
+
$CHILD_STATUS.success?,
|
|
37
|
+
'RuboCop offenses in files you changed (fix them with `rubocop -a`):' \
|
|
38
|
+
"\n\n#{output}"
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
def changed_ruby_files
|
|
44
|
+
# `git status --porcelain` covers unstaged, staged, and untracked in
|
|
45
|
+
# one spawn. Each line is `XY PATH` (or `XY ORIG -> PATH` for
|
|
46
|
+
# renames); PATH starts at index 3. Deleted files are dropped by the
|
|
47
|
+
# File.file? check below (no on-disk entry to lint).
|
|
48
|
+
`git status --porcelain`
|
|
49
|
+
.split("\n")
|
|
50
|
+
.filter_map { |line| line[3..]&.split(' -> ')&.last }
|
|
51
|
+
.select { |path| RUBOCOP_EXTENSIONS.include?(File.extname(path)) && File.file?(path) }
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
data/lib/glib/test_helpers.rb
CHANGED
|
@@ -4,6 +4,12 @@ module Glib
|
|
|
4
4
|
|
|
5
5
|
included do
|
|
6
6
|
extend ClassMethods
|
|
7
|
+
|
|
8
|
+
# Auto-load ChangedFilesRubocop only when this module is mixed into a
|
|
9
|
+
# test class. AllHelpers also includes Glib::TestHelpers into view
|
|
10
|
+
# helper modules in production controllers; in that context
|
|
11
|
+
# ActiveSupport::TestCase is not loaded, so we skip the require.
|
|
12
|
+
require 'glib/test/changed_files_rubocop' if defined?(::ActiveSupport::TestCase)
|
|
7
13
|
end
|
|
8
14
|
|
|
9
15
|
module ClassMethods
|
|
@@ -33,7 +39,17 @@ module Glib
|
|
|
33
39
|
def response_assert_equal
|
|
34
40
|
expected = __get_previous_result_from_git
|
|
35
41
|
result = __log_controller_data(response.body)
|
|
36
|
-
|
|
42
|
+
# The message is a proc so the `git diff` subprocess only runs on FAILURE
|
|
43
|
+
# (a positional string message is built eagerly — one shell-out per
|
|
44
|
+
# passing assertion, and needless git-index contention under parallel
|
|
45
|
+
# workers). The path must be QUOTED: log filenames derive from test
|
|
46
|
+
# names, and an unquoted name containing `->` makes the shell treat `>`
|
|
47
|
+
# as a redirection, creating stray empty files in the app root.
|
|
48
|
+
failure_message = proc do
|
|
49
|
+
diff = __git_is_available? ? `git diff "#{File.join(__controller_log_dir, __controller_log_file)}"` : ''
|
|
50
|
+
"Result mismatch! #{diff}"
|
|
51
|
+
end
|
|
52
|
+
assert_equal JSON.parse(expected), JSON.parse(result), failure_message
|
|
37
53
|
end
|
|
38
54
|
|
|
39
55
|
def crawl_json_pages(user, log_file: nil, dump_actions: false, dump_path: nil, skip_similar_page: false, &block)
|
data/lib/glib-web.rb
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
require 'glib/version'
|
|
2
1
|
if defined?(::Rails)
|
|
3
2
|
require 'glib/engine'
|
|
4
3
|
require 'glib/snapshot'
|
|
@@ -10,6 +9,8 @@ require 'glib/dynamic_text'
|
|
|
10
9
|
require 'glib/test_helpers'
|
|
11
10
|
require 'glib/test/parallel_coverage'
|
|
12
11
|
require 'glib/integration_test'
|
|
12
|
+
require 'glib/last_resort_unit_test'
|
|
13
|
+
require 'glib/non_http_integration_test'
|
|
13
14
|
|
|
14
15
|
require 'glib/time_freezable_mailer'
|
|
15
16
|
require 'glib/time_returning_mailer'
|
metadata
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: glib-web
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 5.
|
|
4
|
+
version: 5.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ''
|
|
8
|
+
autorequire:
|
|
8
9
|
bindir: bin
|
|
9
10
|
cert_chain: []
|
|
10
11
|
date: 2019-10-04 00:00:00.000000000 Z
|
|
@@ -149,6 +150,7 @@ dependencies:
|
|
|
149
150
|
- - ">="
|
|
150
151
|
- !ruby/object:Gem::Version
|
|
151
152
|
version: '0'
|
|
153
|
+
description:
|
|
152
154
|
email: ''
|
|
153
155
|
executables: []
|
|
154
156
|
extensions: []
|
|
@@ -511,7 +513,9 @@ files:
|
|
|
511
513
|
- lib/glib/json_crawler/coverage.rb
|
|
512
514
|
- lib/glib/json_crawler/http.rb
|
|
513
515
|
- lib/glib/json_crawler/router.rb
|
|
516
|
+
- lib/glib/last_resort_unit_test.rb
|
|
514
517
|
- lib/glib/mailer_tester.rb
|
|
518
|
+
- lib/glib/non_http_integration_test.rb
|
|
515
519
|
- lib/glib/rubocop.rb
|
|
516
520
|
- lib/glib/rubocop/cops/json_ui/base_nested_parameter.rb
|
|
517
521
|
- lib/glib/rubocop/cops/json_ui/nested_action_parameter.rb
|
|
@@ -520,15 +524,17 @@ files:
|
|
|
520
524
|
- lib/glib/rubocop/cops/multiline_method_call_style.rb
|
|
521
525
|
- lib/glib/rubocop/cops/test_name_parentheses.rb
|
|
522
526
|
- lib/glib/snapshot.rb
|
|
527
|
+
- lib/glib/test/changed_files_rubocop.rb
|
|
523
528
|
- lib/glib/test/parallel_coverage.rb
|
|
524
529
|
- lib/glib/test_helpers.rb
|
|
525
530
|
- lib/glib/time_freezable_mailer.rb
|
|
526
531
|
- lib/glib/time_returning_mailer.rb
|
|
527
532
|
- lib/glib/value.rb
|
|
528
|
-
- lib/glib/version.rb
|
|
529
533
|
- lib/tasks/db.rake
|
|
534
|
+
homepage:
|
|
530
535
|
licenses: []
|
|
531
536
|
metadata: {}
|
|
537
|
+
post_install_message:
|
|
532
538
|
rdoc_options: []
|
|
533
539
|
require_paths:
|
|
534
540
|
- lib
|
|
@@ -543,7 +549,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
543
549
|
- !ruby/object:Gem::Version
|
|
544
550
|
version: '0'
|
|
545
551
|
requirements: []
|
|
546
|
-
rubygems_version: 4.
|
|
552
|
+
rubygems_version: 3.4.6
|
|
553
|
+
signing_key:
|
|
547
554
|
specification_version: 4
|
|
548
555
|
summary: ''
|
|
549
556
|
test_files: []
|