constable-rails 0.1.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.
Files changed (76) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +88 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +515 -0
  5. data/exe/constable +7 -0
  6. data/lib/constable/case.rb +336 -0
  7. data/lib/constable/cli.rb +475 -0
  8. data/lib/constable/cold_case/minitest.rb +342 -0
  9. data/lib/constable/cold_case/rspec.rb +334 -0
  10. data/lib/constable/cold_case.rb +280 -0
  11. data/lib/constable/config.rb +125 -0
  12. data/lib/constable/coverage.rb +951 -0
  13. data/lib/constable/diff.rb +212 -0
  14. data/lib/constable/dsl.rb +833 -0
  15. data/lib/constable/identity.rb +121 -0
  16. data/lib/constable/importer/modernizer.rb +860 -0
  17. data/lib/constable/importer/reopener.rb +468 -0
  18. data/lib/constable/importer.rb +51 -0
  19. data/lib/constable/investigation.rb +67 -0
  20. data/lib/constable/isolation.rb +171 -0
  21. data/lib/constable/jail.rb +399 -0
  22. data/lib/constable/log_router.rb +197 -0
  23. data/lib/constable/matchers.rb +834 -0
  24. data/lib/constable/order_audit.rb +130 -0
  25. data/lib/constable/rails_support.rb +213 -0
  26. data/lib/constable/railtie.rb +36 -0
  27. data/lib/constable/registry.rb +57 -0
  28. data/lib/constable/reporter.rb +625 -0
  29. data/lib/constable/result.rb +149 -0
  30. data/lib/constable/runner.rb +697 -0
  31. data/lib/constable/selection.rb +205 -0
  32. data/lib/constable/storage/adapter.rb +91 -0
  33. data/lib/constable/storage/mysql_adapter.rb +125 -0
  34. data/lib/constable/storage/postgres_adapter.rb +125 -0
  35. data/lib/constable/storage/sqlite_adapter.rb +84 -0
  36. data/lib/constable/storage.rb +847 -0
  37. data/lib/constable/version.rb +5 -0
  38. data/lib/constable/warrants.rb +290 -0
  39. data/lib/constable-rails.rb +16 -0
  40. data/lib/constable.rb +151 -0
  41. data/lib/generators/constable/base.rb +99 -0
  42. data/lib/generators/constable/channel/channel_generator.rb +20 -0
  43. data/lib/generators/constable/channel/templates/channel_case.rb.tt +29 -0
  44. data/lib/generators/constable/controller/controller_generator.rb +25 -0
  45. data/lib/generators/constable/controller/templates/controller_case.rb.tt +32 -0
  46. data/lib/generators/constable/generator/generator_generator.rb +31 -0
  47. data/lib/generators/constable/generator/templates/generator_case.rb.tt +28 -0
  48. data/lib/generators/constable/helper/helper_generator.rb +23 -0
  49. data/lib/generators/constable/helper/templates/helper_case.rb.tt +19 -0
  50. data/lib/generators/constable/import_generator.rb +137 -0
  51. data/lib/generators/constable/install_generator.rb +188 -0
  52. data/lib/generators/constable/integration/integration_generator.rb +27 -0
  53. data/lib/generators/constable/integration/templates/request_case.rb.tt +22 -0
  54. data/lib/generators/constable/job/job_generator.rb +20 -0
  55. data/lib/generators/constable/job/templates/job_case.rb.tt +33 -0
  56. data/lib/generators/constable/mailbox/mailbox_generator.rb +20 -0
  57. data/lib/generators/constable/mailbox/templates/mailbox_case.rb.tt +26 -0
  58. data/lib/generators/constable/mailer/mailer_generator.rb +32 -0
  59. data/lib/generators/constable/mailer/templates/mailer_case.rb.tt +34 -0
  60. data/lib/generators/constable/mailer/templates/preview.rb.tt +14 -0
  61. data/lib/generators/constable/model/model_generator.rb +31 -0
  62. data/lib/generators/constable/model/templates/model_case.rb.tt +37 -0
  63. data/lib/generators/constable/resource/resource_generator.rb +27 -0
  64. data/lib/generators/constable/scaffold/scaffold_generator.rb +42 -0
  65. data/lib/generators/constable/scaffold/templates/api_controller_case.rb.tt +54 -0
  66. data/lib/generators/constable/scaffold/templates/controller_case.rb.tt +70 -0
  67. data/lib/generators/constable/scaffold/templates/system_case.rb.tt +53 -0
  68. data/lib/generators/constable/system/system_generator.rb +20 -0
  69. data/lib/generators/constable/system/templates/system_case.rb.tt +18 -0
  70. data/lib/generators/constable/templates/authenticatable.rb.tt +31 -0
  71. data/lib/generators/constable/templates/case_helper.rb.tt +179 -0
  72. data/lib/generators/constable/templates/config.yml.tt +67 -0
  73. data/lib/generators/constable/templates/example_case.rb.tt +56 -0
  74. data/lib/generators/constable/templates/matchers.rb.tt +36 -0
  75. data/lib/generators/constable/templates/rubocop.yml.tt +12 -0
  76. metadata +209 -0
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A worked example, so `constable test` does something the moment you install the
4
+ # gem. Delete it once you have a real case file -- or keep it as a scratchpad for
5
+ # checking that the harness itself still boots.
6
+ #
7
+ # The vocabulary is the API:
8
+ #
9
+ # investigate a test (it / def test_) -- a plain string description
10
+ # witness a fixture (let) -- memoized per test, never per process
11
+ # briefing setup (before / setup) -- runs before every investigation
12
+ # docket grouping (nested describe) -- scoping sugar, no shared state
13
+ # attest an assertion (expect) -- sugar over assert_*, which still works
14
+ class ExampleCase < UnitCase
15
+ # A witness is memoized inside one investigation and thrown away at the end of
16
+ # it. Name it five times in a test, pay for it once; the next test builds its
17
+ # own. That boundary is the whole reason there is no cross-test cache.
18
+ witness(:badge) { { number: 1, name: "Constable" } }
19
+
20
+ # A briefing runs before *every* investigation below, against a brand-new
21
+ # instance. There is no before(:all) to reach for, which is why nothing an
22
+ # investigation does here can reach the next one.
23
+ briefing do
24
+ @notes = []
25
+ end
26
+
27
+ investigate "runs the moment the gem is installed" do
28
+ @notes << badge[:name]
29
+
30
+ attest(@notes).to eq(["Constable"])
31
+ end
32
+
33
+ investigate "hands the next investigation a clean slate" do
34
+ # @notes was emptied by the briefing again, and the previous investigation's
35
+ # push is nowhere to be found. This passes in any order, on any machine.
36
+ attest(@notes).to be_empty
37
+ end
38
+
39
+ # Dockets group related investigations and scope setup to the group. They
40
+ # introduce no shared state -- each investigation inside still gets its own
41
+ # fresh instance, its own briefings, its own witnesses.
42
+ docket "with a nested docket" do
43
+ briefing { @notes << "on the beat" }
44
+
45
+ investigate "inherits the outer briefing before running its own" do
46
+ attest(@notes).to eq(["on the beat"])
47
+ end
48
+ end
49
+
50
+ # The assert_* primitives are always available alongside attest. attest is
51
+ # sugar, never the only road.
52
+ investigate "still speaks plain assertions" do
53
+ assert_equal 1, badge[:number]
54
+ assert_includes badge[:name], "Const"
55
+ end
56
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Custom matchers.
4
+ #
5
+ # `attest(actual).to matcher` is sugar over the plain assert_* primitives, which
6
+ # are always available too -- attest never becomes the only way to say a thing.
7
+ # A matcher block returns truthy to pass:
8
+ #
9
+ # Constable::Matchers.define(:name) { |actual, *args| ... }
10
+ #
11
+ # or returns [passed, message, context] when it can say something more useful
12
+ # than "expected it to match" on failure. `context` is printed under the failure
13
+ # in the run summary, which is where a response body or a record's attributes
14
+ # earn their keep.
15
+ #
16
+ # Everything in test/support/ is loaded by test/case_helper.rb before any case
17
+ # file, so a matcher defined here is available to every case in the suite.
18
+
19
+ Constable::Matchers.define(:be_created) do |response|
20
+ response.status == 201
21
+ end
22
+
23
+ Constable::Matchers.define(:exist) do |model_class, attrs|
24
+ model_class.exists?(attrs)
25
+ end
26
+
27
+ # The richer form, for when the failure message is the difference between a
28
+ # five-second fix and a five-minute one:
29
+ #
30
+ # Constable::Matchers.define(:have_role) do |user, role|
31
+ # [
32
+ # user.roles.include?(role),
33
+ # "expected #{user.class}##{user.id} to have role #{role.inspect}",
34
+ # { roles: user.roles }
35
+ # ]
36
+ # end
@@ -0,0 +1,12 @@
1
+ # Constable's linter.
2
+ #
3
+ # Nondeterminism is caught here, at edit time, rather than discovered in CI three
4
+ # weeks later: bare `sleep`, unfrozen Time.now/Date.today, unstubbed network
5
+ # calls, shared mutable state, conditional assertions, retry helpers.
6
+ #
7
+ # The cops are scoped to native Constable::Case files only. A file inheriting
8
+ # from Constable::ColdCase::* is exempt by design -- cold cases are how a legacy
9
+ # suite adopts Constable without a rewrite, and holding them to native rules on
10
+ # day one would defeat that entirely.
11
+ require:
12
+ - rubocop-constable
metadata ADDED
@@ -0,0 +1,209 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: constable-rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ray Hughes
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2026-09-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '7.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '7.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: parser
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '3.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '3.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: railties
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '7.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '7.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: sqlite3
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '1.6'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '1.6'
69
+ - !ruby/object:Gem::Dependency
70
+ name: thor
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '1.2'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '1.2'
83
+ description: |
84
+ Constable replaces RSpec/Minitest for Rails apps that want tests to be fast and
85
+ non-flaky by construction. Every test is isolated by default, nondeterminism is a
86
+ lint error rather than a CI surprise, and every escape hatch is reported until
87
+ someone deals with it. Existing RSpec and Minitest suites adopt it with a one-line
88
+ change per file and zero rewriting -- cold cases run verbatim through their original
89
+ engine while native cases run under strict rules, side by side in one run.
90
+
91
+ Ships with a case-file DSL (investigate/witness/briefing/docket), transactional
92
+ isolation, flake history with rename-surviving content-hash identity, a jail and
93
+ parole docket for legacy red suites, warrants for automatic flake detection, diff-based
94
+ coverage, parallel workers, git-diff test selection, and a RuboCop extension.
95
+
96
+ The gem is published as "constable-rails"; everything inside it -- the module, the
97
+ CLI, the config directory -- is simply "constable".
98
+ email:
99
+ - r.hughes2136@gmail.com
100
+ executables:
101
+ - constable
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - CHANGELOG.md
106
+ - LICENSE.txt
107
+ - README.md
108
+ - exe/constable
109
+ - lib/constable-rails.rb
110
+ - lib/constable.rb
111
+ - lib/constable/case.rb
112
+ - lib/constable/cli.rb
113
+ - lib/constable/cold_case.rb
114
+ - lib/constable/cold_case/minitest.rb
115
+ - lib/constable/cold_case/rspec.rb
116
+ - lib/constable/config.rb
117
+ - lib/constable/coverage.rb
118
+ - lib/constable/diff.rb
119
+ - lib/constable/dsl.rb
120
+ - lib/constable/identity.rb
121
+ - lib/constable/importer.rb
122
+ - lib/constable/importer/modernizer.rb
123
+ - lib/constable/importer/reopener.rb
124
+ - lib/constable/investigation.rb
125
+ - lib/constable/isolation.rb
126
+ - lib/constable/jail.rb
127
+ - lib/constable/log_router.rb
128
+ - lib/constable/matchers.rb
129
+ - lib/constable/order_audit.rb
130
+ - lib/constable/rails_support.rb
131
+ - lib/constable/railtie.rb
132
+ - lib/constable/registry.rb
133
+ - lib/constable/reporter.rb
134
+ - lib/constable/result.rb
135
+ - lib/constable/runner.rb
136
+ - lib/constable/selection.rb
137
+ - lib/constable/storage.rb
138
+ - lib/constable/storage/adapter.rb
139
+ - lib/constable/storage/mysql_adapter.rb
140
+ - lib/constable/storage/postgres_adapter.rb
141
+ - lib/constable/storage/sqlite_adapter.rb
142
+ - lib/constable/version.rb
143
+ - lib/constable/warrants.rb
144
+ - lib/generators/constable/base.rb
145
+ - lib/generators/constable/channel/channel_generator.rb
146
+ - lib/generators/constable/channel/templates/channel_case.rb.tt
147
+ - lib/generators/constable/controller/controller_generator.rb
148
+ - lib/generators/constable/controller/templates/controller_case.rb.tt
149
+ - lib/generators/constable/generator/generator_generator.rb
150
+ - lib/generators/constable/generator/templates/generator_case.rb.tt
151
+ - lib/generators/constable/helper/helper_generator.rb
152
+ - lib/generators/constable/helper/templates/helper_case.rb.tt
153
+ - lib/generators/constable/import_generator.rb
154
+ - lib/generators/constable/install_generator.rb
155
+ - lib/generators/constable/integration/integration_generator.rb
156
+ - lib/generators/constable/integration/templates/request_case.rb.tt
157
+ - lib/generators/constable/job/job_generator.rb
158
+ - lib/generators/constable/job/templates/job_case.rb.tt
159
+ - lib/generators/constable/mailbox/mailbox_generator.rb
160
+ - lib/generators/constable/mailbox/templates/mailbox_case.rb.tt
161
+ - lib/generators/constable/mailer/mailer_generator.rb
162
+ - lib/generators/constable/mailer/templates/mailer_case.rb.tt
163
+ - lib/generators/constable/mailer/templates/preview.rb.tt
164
+ - lib/generators/constable/model/model_generator.rb
165
+ - lib/generators/constable/model/templates/model_case.rb.tt
166
+ - lib/generators/constable/resource/resource_generator.rb
167
+ - lib/generators/constable/scaffold/scaffold_generator.rb
168
+ - lib/generators/constable/scaffold/templates/api_controller_case.rb.tt
169
+ - lib/generators/constable/scaffold/templates/controller_case.rb.tt
170
+ - lib/generators/constable/scaffold/templates/system_case.rb.tt
171
+ - lib/generators/constable/system/system_generator.rb
172
+ - lib/generators/constable/system/templates/system_case.rb.tt
173
+ - lib/generators/constable/templates/authenticatable.rb.tt
174
+ - lib/generators/constable/templates/case_helper.rb.tt
175
+ - lib/generators/constable/templates/config.yml.tt
176
+ - lib/generators/constable/templates/example_case.rb.tt
177
+ - lib/generators/constable/templates/matchers.rb.tt
178
+ - lib/generators/constable/templates/rubocop.yml.tt
179
+ homepage: https://github.com/Ray-Hughes/constable
180
+ licenses:
181
+ - MIT
182
+ metadata:
183
+ homepage_uri: https://github.com/Ray-Hughes/constable
184
+ source_code_uri: https://github.com/Ray-Hughes/constable
185
+ changelog_uri: https://github.com/Ray-Hughes/constable/blob/main/CHANGELOG.md
186
+ bug_tracker_uri: https://github.com/Ray-Hughes/constable/issues
187
+ documentation_uri: https://github.com/Ray-Hughes/constable#readme
188
+ rubygems_mfa_required: 'true'
189
+ post_install_message:
190
+ rdoc_options: []
191
+ require_paths:
192
+ - lib
193
+ required_ruby_version: !ruby/object:Gem::Requirement
194
+ requirements:
195
+ - - ">="
196
+ - !ruby/object:Gem::Version
197
+ version: 3.1.0
198
+ required_rubygems_version: !ruby/object:Gem::Requirement
199
+ requirements:
200
+ - - ">="
201
+ - !ruby/object:Gem::Version
202
+ version: '0'
203
+ requirements: []
204
+ rubygems_version: 3.4.10
205
+ signing_key:
206
+ specification_version: 4
207
+ summary: A strict Rails testing framework where fast and non-flaky are structural,
208
+ not disciplinary.
209
+ test_files: []