plutonium 0.46.0 → 0.48.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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/skills/plutonium/SKILL.md +4 -0
  3. data/.claude/skills/plutonium-interaction/SKILL.md +23 -0
  4. data/.claude/skills/plutonium-nested-resources/SKILL.md +10 -0
  5. data/.claude/skills/plutonium-testing/SKILL.md +268 -0
  6. data/.yarnrc.yml +1 -0
  7. data/CHANGELOG.md +23 -0
  8. data/Rakefile +10 -1
  9. data/app/assets/plutonium.css +1 -1
  10. data/docs/.vitepress/config.ts +6 -0
  11. data/docs/guides/nested-resources.md +10 -0
  12. data/docs/guides/testing.md +154 -0
  13. data/docs/reference/controller/index.md +9 -4
  14. data/docs/superpowers/plans/2026-04-14-plutonium-testing.md +2046 -0
  15. data/docs/superpowers/plans/2026-04-14-plutonium-testing.md.tasks.json +21 -0
  16. data/docs/superpowers/specs/2026-04-14-plutonium-testing-design.md +364 -0
  17. data/gemfiles/rails_8.1.gemfile.lock +27 -1
  18. data/lib/generators/pu/test/install/install_generator.rb +34 -0
  19. data/lib/generators/pu/test/install/templates/plutonium_testing.rb.tt +14 -0
  20. data/lib/generators/pu/test/scaffold/scaffold_generator.rb +55 -0
  21. data/lib/generators/pu/test/scaffold/templates/integration_test.rb.tt +65 -0
  22. data/lib/plutonium/action/interactive.rb +2 -1
  23. data/lib/plutonium/core/controller.rb +18 -1
  24. data/lib/plutonium/helpers/turbo_stream_actions_helper.rb +20 -1
  25. data/lib/plutonium/testing/auth_helpers.rb +62 -0
  26. data/lib/plutonium/testing/dsl.rb +73 -0
  27. data/lib/plutonium/testing/nested_resource.rb +58 -0
  28. data/lib/plutonium/testing/portal_access.rb +49 -0
  29. data/lib/plutonium/testing/resource_crud.rb +104 -0
  30. data/lib/plutonium/testing/resource_definition.rb +61 -0
  31. data/lib/plutonium/testing/resource_interaction.rb +51 -0
  32. data/lib/plutonium/testing/resource_model.rb +53 -0
  33. data/lib/plutonium/testing/resource_policy.rb +72 -0
  34. data/lib/plutonium/testing.rb +16 -0
  35. data/lib/plutonium/ui/action_button.rb +1 -1
  36. data/lib/plutonium/version.rb +1 -1
  37. data/lib/plutonium.rb +2 -0
  38. data/package.json +1 -1
  39. data/plutonium.gemspec +2 -0
  40. data/yarn.lock +6037 -3893
  41. metadata +50 -2
@@ -0,0 +1,21 @@
1
+ {
2
+ "planPath": "docs/superpowers/plans/2026-04-14-plutonium-testing.md",
3
+ "tasks": [
4
+ {"id": 1, "subject": "Create Plutonium::Testing module skeleton and autoload entry", "status": "pending", "planTaskNumber": 1},
5
+ {"id": 2, "subject": "Implement shared DSL and portal resolution", "status": "pending", "blockedBy": [1], "planTaskNumber": 2},
6
+ {"id": 10, "subject": "Implement portal-scoped AuthHelpers", "status": "pending", "blockedBy": [2], "planTaskNumber": 3},
7
+ {"id": 3, "subject": "Implement ResourceCrud concern", "status": "pending", "blockedBy": [2, 10], "planTaskNumber": 4},
8
+ {"id": 4, "subject": "Implement ResourcePolicy concern", "status": "pending", "blockedBy": [2], "planTaskNumber": 5},
9
+ {"id": 5, "subject": "Implement ResourceDefinition concern", "status": "pending", "blockedBy": [2, 3], "planTaskNumber": 6},
10
+ {"id": 6, "subject": "Implement ResourceInteraction concern", "status": "pending", "blockedBy": [1], "planTaskNumber": 7},
11
+ {"id": 7, "subject": "Implement ResourceModel concern", "status": "pending", "blockedBy": [2], "planTaskNumber": 8},
12
+ {"id": 8, "subject": "Implement NestedResource concern", "status": "pending", "blockedBy": [2, 10], "planTaskNumber": 9},
13
+ {"id": 9, "subject": "Implement PortalAccess concern", "status": "pending", "blockedBy": [10], "planTaskNumber": 10},
14
+ {"id": 11, "subject": "Implement pu:test:install generator", "status": "pending", "blockedBy": [1], "planTaskNumber": 11},
15
+ {"id": 12, "subject": "Implement pu:test:scaffold generator", "status": "pending", "blockedBy": [1], "planTaskNumber": 12},
16
+ {"id": 14, "subject": "Write plutonium-testing skill documentation", "status": "pending", "blockedBy": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], "planTaskNumber": 13},
17
+ {"id": 15, "subject": "Add VitePress docs guide for testing", "status": "pending", "blockedBy": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], "planTaskNumber": 14},
18
+ {"id": 13, "subject": "Migrate in-repo shared_tests to use Plutonium::Testing concerns", "status": "pending", "blockedBy": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], "planTaskNumber": 15}
19
+ ],
20
+ "lastUpdated": "2026-04-14T00:00:00Z"
21
+ }
@@ -0,0 +1,364 @@
1
+ # Plutonium::Testing — Design Spec
2
+
3
+ **Date:** 2026-04-14
4
+ **Status:** Approved (pre-implementation)
5
+ **Audience:** End-app developers using Plutonium
6
+
7
+ ---
8
+
9
+ ## Goal
10
+
11
+ Ship `Plutonium::Testing` — a public, opt-in collection of Minitest concerns that give Plutonium app developers default test coverage for resources, policies, definitions, interactions, models, nested scoping, portal access, and authentication. Pair the module with a `plutonium-testing` skill and a Rails generator (`pu:test:scaffold`) that produces ready-to-run test files per (resource × portal) pairing.
12
+
13
+ ## Non-Goals
14
+
15
+ - RSpec support (Minitest only for first cut).
16
+ - A test data builder / factory layer (out of scope; callers wire their own factories or fixtures).
17
+ - Replacing Plutonium's own internal `test/support/shared_tests/` ahead of the public API landing — the migration is the *last* implementation step and serves as dogfooding.
18
+
19
+ ## Approach
20
+
21
+ - **Loading model:** opt-in `require "plutonium/testing"` in the consumer's `test/test_helper.rb`. No autoload, no production cost. Mirrors `ActiveSupport::Testing::*` conventions.
22
+ - **Granularity:** one concern per category. Callers `include` exactly what they need. No umbrella module.
23
+ - **DSL + stub methods:** declarative config (`resource_tests_for Post, portal: :admin`) for shape; abstract stub methods (raising `NotImplementedError`) for test data the caller must provide.
24
+ - **Portal-centric:** the `portal:` symbol is the single unit of configuration. It resolves to path prefix, default sign-in helper, expected scoping, and allowed action set by introspecting the mounted portal engine. Same resource × different portal = different test file.
25
+
26
+ ---
27
+
28
+ ## Architecture
29
+
30
+ ### File Layout
31
+
32
+ ```
33
+ lib/plutonium/
34
+ testing.rb # top-level require; loads submodules
35
+ testing/
36
+ dsl.rb # shared `resource_tests_for` + portal resolution
37
+ auth_helpers.rb # login_as / sign_out / with_portal (portal-aware)
38
+ resource_crud.rb # integration CRUD matrix
39
+ resource_policy.rb # permit? × action × role; relation_scope filtering
40
+ resource_definition.rb # fields/inputs/displays/columns/scopes smoke
41
+ resource_interaction.rb # interaction outcome assertions
42
+ resource_model.rb # associated_with, SGID, has_cents
43
+ nested_resource.rb # tenant-scoped CRUD + boundary assertions
44
+ portal_access.rb # cross-portal access boundaries
45
+
46
+ lib/generators/pu/test/
47
+ install/install_generator.rb # one-time setup
48
+ install/templates/plutonium_testing.rb.tt
49
+ scaffold/scaffold_generator.rb # per-resource × portal scaffold
50
+ scaffold/templates/integration_test.rb.tt
51
+ scaffold/templates/policy_test.rb.tt
52
+ scaffold/templates/definition_test.rb.tt
53
+
54
+ test/plutonium/testing/ # tests for the testing module itself
55
+ dsl_test.rb
56
+ auth_helpers_test.rb
57
+ resource_crud_test.rb
58
+ resource_policy_test.rb
59
+ resource_definition_test.rb
60
+ resource_interaction_test.rb
61
+ resource_model_test.rb
62
+ nested_resource_test.rb
63
+ portal_access_test.rb
64
+
65
+ test/generators/pu/test/
66
+ install_generator_test.rb
67
+ scaffold_generator_test.rb
68
+
69
+ .claude/skills/plutonium-testing/
70
+ SKILL.md
71
+
72
+ docs/guides/testing.md
73
+ ```
74
+
75
+ ### Loading
76
+
77
+ `lib/plutonium/testing.rb` is the entry point:
78
+
79
+ ```ruby
80
+ require "plutonium/testing/dsl"
81
+ require "plutonium/testing/auth_helpers"
82
+ require "plutonium/testing/resource_crud"
83
+ require "plutonium/testing/resource_policy"
84
+ require "plutonium/testing/resource_definition"
85
+ require "plutonium/testing/resource_interaction"
86
+ require "plutonium/testing/resource_model"
87
+ require "plutonium/testing/nested_resource"
88
+ require "plutonium/testing/portal_access"
89
+ ```
90
+
91
+ Consumers add `require "plutonium/testing"` to `test/test_helper.rb` (the `pu:test:install` generator does this for them).
92
+
93
+ ---
94
+
95
+ ## DSL
96
+
97
+ `Plutonium::Testing::DSL` is included by every concern. Provides one class-level method:
98
+
99
+ ```ruby
100
+ resource_tests_for ResourceClass,
101
+ portal: :admin, # required: portal symbol
102
+ path_prefix: "/admin", # optional: explicit override
103
+ parent: :organization, # optional: nested-resource parent
104
+ actions: %i[index show new create edit update destroy], # optional: opt-in set
105
+ skip: %i[destroy] # optional: opt-out individual tests
106
+ ```
107
+
108
+ The portal symbol drives:
109
+
110
+ | Derived from portal | Example for `:admin` | Example for `:org` |
111
+ |---|---|---|
112
+ | `path_prefix` | `/admin` | `/org/:organization_id` |
113
+ | Default sign-in | admin Rodauth strategy | org-member Rodauth strategy |
114
+ | Expected entity scoping | unscoped | scoped to `@organization` |
115
+ | Allowed action set | from definition | from definition |
116
+
117
+ DSL stores the current portal in a test-class-local attr; `AuthHelpers` reads it as default when no `portal:` kwarg is given.
118
+
119
+ ### Cross-portal tests
120
+
121
+ For tests that touch multiple portals (`PortalAccess` concern), `portal:` is an explicit kwarg on helper calls:
122
+
123
+ ```ruby
124
+ login_as(@admin, portal: :admin)
125
+ login_as(@user, portal: :org)
126
+ with_portal(:org) { get "/org/posts" } # block form
127
+ ```
128
+
129
+ ---
130
+
131
+ ## Concerns
132
+
133
+ ### `Plutonium::Testing::ResourceCrud`
134
+
135
+ Generates index / show / new / create / edit / update / destroy integration tests against the portal-mounted resource.
136
+
137
+ **Stubs (caller must implement):**
138
+ ```ruby
139
+ def create_resource! # -> persisted record
140
+ def valid_create_params # -> Hash for POST
141
+ def valid_update_params # -> Hash for PATCH
142
+ ```
143
+
144
+ Sign-in is automatic from the portal's auth strategy unless the caller overrides `sign_in_for_tests(account, portal:)`.
145
+
146
+ ### `Plutonium::Testing::ResourcePolicy`
147
+
148
+ Asserts the `permit?` matrix across action × role and `relation_scope` filtering.
149
+
150
+ **Stubs:**
151
+ ```ruby
152
+ def policy_roles # -> { admin: -> { @admin }, member: -> { @user } }
153
+ def policy_record # -> record instance under test
154
+ def policy_matrix # -> { index: %i[admin member], destroy: %i[admin] }
155
+ ```
156
+
157
+ ### `Plutonium::Testing::ResourceDefinition`
158
+
159
+ Smoke-tests that all registered fields/inputs/displays/columns/scopes/filters render without error against a persisted record. Introspects the definition class via `Plutonium::Definition::DefineableProps`. **No caller stubs required** for the happy path.
160
+
161
+ ### `Plutonium::Testing::ResourceInteraction`
162
+
163
+ Outcome-assertion helpers for `Plutonium::Resource::Interaction` subclasses.
164
+
165
+ **Helpers:**
166
+ - `assert_interaction_success(interaction_class, **input)`
167
+ - `assert_interaction_failure(interaction_class, **input)`
168
+ - `assert_interaction_redirect(interaction_class, to:, **input)`
169
+ - `assert_interaction_renders(interaction_class, view:, **input)`
170
+
171
+ **Stubs:**
172
+ ```ruby
173
+ def interaction_class
174
+ def valid_interaction_input
175
+ ```
176
+
177
+ ### `Plutonium::Testing::ResourceModel`
178
+
179
+ Covers `associated_with` scope behavior, SGID routing, and `has_cents` money helpers. DSL flags select which features to test:
180
+
181
+ ```ruby
182
+ resource_tests_for Post, portal: :admin,
183
+ associated_with: :organization,
184
+ sgid_routing: true,
185
+ has_cents: %i[price]
186
+ ```
187
+
188
+ Only generates test methods for enabled features.
189
+
190
+ ### `Plutonium::Testing::NestedResource`
191
+
192
+ Same CRUD matrix as `ResourceCrud` but asserts scope boundaries: index excludes records from sibling tenants; show on a sibling-tenant record returns 404.
193
+
194
+ **Stubs:**
195
+ ```ruby
196
+ def parent_record! # -> persisted parent/tenant
197
+ def other_parent_record! # -> a different tenant for boundary tests
198
+ ```
199
+
200
+ ### `Plutonium::Testing::PortalAccess`
201
+
202
+ Cross-portal access boundaries.
203
+
204
+ **DSL:**
205
+ ```ruby
206
+ portal_access_matrix \
207
+ admin: %i[admin_portal],
208
+ member: %i[org_portal storefront_portal]
209
+ ```
210
+
211
+ Asserts unauthorized portals return 403 or redirect to login.
212
+
213
+ ---
214
+
215
+ ## Auth Helpers
216
+
217
+ `Plutonium::Testing::AuthHelpers` is included transitively by every concern.
218
+
219
+ **Public API:**
220
+ ```ruby
221
+ login_as(account) # uses portal from DSL
222
+ login_as(account, portal: :admin) # explicit override
223
+ sign_out # uses portal from DSL
224
+ sign_out(portal: :admin)
225
+ current_account # uses portal from DSL
226
+ current_account(portal: :admin)
227
+ with_portal(:org) { ... } # scoped portal switch
228
+ ```
229
+
230
+ **Implementation:**
231
+ - Looks up the portal's declared auth strategy from the mounted portal engine config.
232
+ - For Rodauth (stock Plutonium): fakes the session cookie directly; no full login round-trip.
233
+ - **Override hook** for non-Rodauth apps: caller defines `sign_in_for_tests(account, portal:)` and `AuthHelpers` defers to it.
234
+
235
+ The `pu:test:install` generator scaffolds a commented-out override in `test/support/plutonium_testing.rb`.
236
+
237
+ ---
238
+
239
+ ## Generators
240
+
241
+ ### `pu:test:install`
242
+
243
+ One-time project setup. Idempotent.
244
+
245
+ - Adds `require "plutonium/testing"` to `test/test_helper.rb` (no-op if present).
246
+ - Creates `test/support/plutonium_testing.rb` with commented-out override stubs for non-Rodauth auth.
247
+
248
+ ### `pu:test:scaffold`
249
+
250
+ Per-resource scaffold. Produces one file per (resource × portal).
251
+
252
+ ```bash
253
+ rails g pu:test:scaffold Blogging::Post --portals=admin,org
254
+ rails g pu:test:scaffold Blogging::Post --portals=admin --concerns=crud,policy,definition
255
+ rails g pu:test:scaffold Blogging::Post --portals=org --parent=organization --dest=blogging
256
+ ```
257
+
258
+ **Flags:**
259
+ - `--portals=admin,org` (required) — emits one file per portal.
260
+ - `--concerns=crud,policy,definition,nested,model,interaction,portal_access` (default: `crud,policy,definition`).
261
+ - `--parent=organization` — wires `NestedResource` parent stub.
262
+ - `--dest=main_app|package_name` — output destination (matches `pu:res:scaffold` convention).
263
+
264
+ **Example output** (`test/integration/admin_portal/blogging_posts_test.rb`):
265
+
266
+ ```ruby
267
+ require "test_helper"
268
+
269
+ class AdminPortal::BloggingPostsTest < ActionDispatch::IntegrationTest
270
+ include Plutonium::Testing::ResourceCrud
271
+ include Plutonium::Testing::ResourcePolicy
272
+ include Plutonium::Testing::ResourceDefinition
273
+
274
+ resource_tests_for Blogging::Post, portal: :admin
275
+
276
+ setup do
277
+ @admin = create_admin! # TODO: replace with your factory
278
+ login_as(@admin)
279
+ end
280
+
281
+ def create_resource!
282
+ Blogging::Post.create!(title: "X", body: "...") # TODO: adjust
283
+ end
284
+
285
+ def valid_create_params
286
+ { title: "New", body: "..." } # TODO: adjust
287
+ end
288
+
289
+ def valid_update_params
290
+ { title: "Updated" } # TODO: adjust
291
+ end
292
+
293
+ def policy_roles
294
+ { admin: -> { @admin } } # TODO: add other roles
295
+ end
296
+
297
+ def policy_record
298
+ create_resource!
299
+ end
300
+
301
+ def policy_matrix
302
+ { index: %i[admin], show: %i[admin], create: %i[admin],
303
+ update: %i[admin], destroy: %i[admin] } # TODO: tighten
304
+ end
305
+ end
306
+ ```
307
+
308
+ Stub method bodies are pre-filled with best-guess values from model introspection (column types, associations).
309
+
310
+ ---
311
+
312
+ ## Skill
313
+
314
+ `.claude/skills/plutonium-testing/SKILL.md` follows the existing skill conventions in `.claude/skills/`.
315
+
316
+ **Frontmatter `description`:**
317
+ > Use BEFORE writing tests for a Plutonium resource, running `pu:test:scaffold`, or including `Plutonium::Testing::*` concerns. Covers the full testing toolkit: CRUD, policy, definition, interaction, model, nested, portal access, and auth helpers.
318
+
319
+ **Sections:**
320
+ 1. When to use
321
+ 2. Quick start (install + scaffold + first run)
322
+ 3. DSL reference (`resource_tests_for` keywords + portal resolution table)
323
+ 4. Concerns catalog (one section per concern with stub contract + example)
324
+ 5. Auth helpers
325
+ 6. Generator reference (`pu:test:install`, `pu:test:scaffold` flags)
326
+ 7. Customization & escape hatches (non-Rodauth auth, skipping defaults, custom assertions)
327
+ 8. Common pitfalls (forgotten stubs, portal mismatch, tenant leakage)
328
+
329
+ The top-level `plutonium` router skill gets one new entry pointing to `plutonium-testing`.
330
+
331
+ ---
332
+
333
+ ## Docs
334
+
335
+ `docs/guides/testing.md` mirrors the skill content for human-facing documentation. Linked from the guides section in `docs/.vitepress/config.ts`.
336
+
337
+ ---
338
+
339
+ ## In-Repo Adoption (Dogfooding)
340
+
341
+ After all concerns and generators land, port the dummy app's tests:
342
+
343
+ - `test/integration/admin_portal/resources_test.rb` and the other portal test files migrate to `Plutonium::Testing::*` concerns.
344
+ - `test/support/shared_tests/blogging_post_tests.rb` and `catalog_product_tests.rb` are deleted or shrunk to anything that doesn't fit the generic concerns.
345
+
346
+ Acceptance: zero coverage loss (compare test method counts before/after) and the entire suite still passes across `rails-7`, `rails-8.0`, `rails-8.1` appraisals.
347
+
348
+ ---
349
+
350
+ ## Implementation Sequence
351
+
352
+ 1. Module skeleton + entry point (`lib/plutonium/testing.rb`).
353
+ 2. Shared DSL + portal resolution.
354
+ 3. Auth helpers.
355
+ 4. Each concern (parallelizable after #2 and #3): ResourceCrud, ResourcePolicy, ResourceDefinition, ResourceInteraction, ResourceModel, NestedResource, PortalAccess.
356
+ 5. Generators: `pu:test:install`, then `pu:test:scaffold`.
357
+ 6. Skill + docs.
358
+ 7. In-repo migration (last — dogfoods the public API).
359
+
360
+ ---
361
+
362
+ ## Open Questions
363
+
364
+ None at design time. Implementation may surface portal-resolution edge cases (engines mounted at non-standard paths, multiple engines per account type) that warrant follow-up.
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- plutonium (0.45.3)
4
+ plutonium (0.47.0)
5
5
  action_policy (~> 0.7.0)
6
6
  listen (~> 3.8)
7
7
  pagy (~> 43.0)
@@ -101,6 +101,8 @@ GEM
101
101
  securerandom (>= 0.3)
102
102
  tzinfo (~> 2.0, >= 2.0.5)
103
103
  uri (>= 0.13.1)
104
+ addressable (2.9.0)
105
+ public_suffix (>= 2.0.2, < 8.0)
104
106
  ansi (1.5.0)
105
107
  appraisal (2.5.0)
106
108
  bundler
@@ -118,6 +120,15 @@ GEM
118
120
  bundler-audit (0.9.3)
119
121
  bundler (>= 1.2.0)
120
122
  thor (~> 1.0)
123
+ capybara (3.40.0)
124
+ addressable
125
+ matrix
126
+ mini_mime (>= 0.1.3)
127
+ nokogiri (~> 1.11)
128
+ rack (>= 1.6.0)
129
+ rack-test (>= 0.6.3)
130
+ regexp_parser (>= 1.5, < 3.0)
131
+ xpath (~> 3.2)
121
132
  chunky_png (1.4.0)
122
133
  combustion (1.5.0)
123
134
  activesupport (>= 3.0.0)
@@ -166,6 +177,7 @@ GEM
166
177
  net-pop
167
178
  net-smtp
168
179
  marcel (1.1.0)
180
+ matrix (0.4.3)
169
181
  mini_mime (1.1.5)
170
182
  minitest (6.0.2)
171
183
  drb (~> 2.0)
@@ -243,6 +255,7 @@ GEM
243
255
  psych (5.3.1)
244
256
  date
245
257
  stringio
258
+ public_suffix (7.0.5)
246
259
  puma (7.2.0)
247
260
  nio4r (~> 2.0)
248
261
  rabl (0.17.0)
@@ -302,6 +315,7 @@ GEM
302
315
  regexp_parser (2.11.3)
303
316
  reline (0.6.3)
304
317
  io-console (~> 0.5)
318
+ rexml (3.4.4)
305
319
  roda (3.102.0)
306
320
  rack
307
321
  rodauth (2.42.0)
@@ -339,7 +353,14 @@ GEM
339
353
  rubocop-ast (>= 1.47.1, < 2.0)
340
354
  ruby-next-core (1.2.0)
341
355
  ruby-progressbar (1.13.0)
356
+ rubyzip (3.2.2)
342
357
  securerandom (0.4.1)
358
+ selenium-webdriver (4.43.0)
359
+ base64 (~> 0.2)
360
+ logger (~> 1.4)
361
+ rexml (~> 3.2, >= 3.2.5)
362
+ rubyzip (>= 1.2.2, < 4.0)
363
+ websocket (~> 1.0)
343
364
  semantic_range (3.1.1)
344
365
  sequel (5.102.0)
345
366
  bigdecimal
@@ -389,11 +410,14 @@ GEM
389
410
  unicode-emoji (4.2.0)
390
411
  uri (1.1.1)
391
412
  useragent (0.16.11)
413
+ websocket (1.2.11)
392
414
  websocket-driver (0.8.0)
393
415
  base64
394
416
  websocket-extensions (>= 0.1.0)
395
417
  websocket-extensions (0.1.5)
396
418
  wisper (2.0.1)
419
+ xpath (3.2.0)
420
+ nokogiri (~> 1.8)
397
421
  yaml (0.4.0)
398
422
  zeitwerk (2.7.5)
399
423
 
@@ -405,6 +429,7 @@ DEPENDENCIES
405
429
  bcrypt
406
430
  brakeman
407
431
  bundle-audit
432
+ capybara
408
433
  combustion
409
434
  importmap-rails
410
435
  minitest
@@ -417,6 +442,7 @@ DEPENDENCIES
417
442
  rodauth-rails
418
443
  rotp
419
444
  rqrcode
445
+ selenium-webdriver
420
446
  sequel-activerecord_connection
421
447
  sqlite3
422
448
  standard
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../lib/plutonium_generators"
4
+
5
+ module Pu
6
+ module Test
7
+ class InstallGenerator < Rails::Generators::Base
8
+ include PlutoniumGenerators::Generator
9
+
10
+ source_root File.expand_path("templates", __dir__)
11
+
12
+ desc "Install Plutonium::Testing scaffolding"
13
+
14
+ def install
15
+ add_require_to_test_helper
16
+ copy_support_file
17
+ end
18
+
19
+ private
20
+
21
+ def add_require_to_test_helper
22
+ helper = "test/test_helper.rb"
23
+ return unless File.exist?(helper)
24
+ line = %(require "plutonium/testing")
25
+ return if File.read(helper).include?(line)
26
+ append_to_file helper, "\n#{line}\n"
27
+ end
28
+
29
+ def copy_support_file
30
+ copy_file "plutonium_testing.rb", "test/support/plutonium_testing.rb"
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Plutonium::Testing project hooks.
4
+ #
5
+ # Override authentication for non-Rodauth setups by defining a top-level helper
6
+ # that gets included into integration tests:
7
+ #
8
+ # module PlutoniumTestingOverrides
9
+ # def sign_in_for_tests(account, portal:)
10
+ # # your custom auth flow here
11
+ # end
12
+ # end
13
+ #
14
+ # ActiveSupport::TestCase.include(PlutoniumTestingOverrides)
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../lib/plutonium_generators"
4
+
5
+ module Pu
6
+ module Test
7
+ class ScaffoldGenerator < Rails::Generators::NamedBase
8
+ include PlutoniumGenerators::Generator
9
+
10
+ source_root File.expand_path("templates", __dir__)
11
+
12
+ desc "Scaffold Plutonium::Testing tests for a resource across one or more portals"
13
+
14
+ class_option :portals, type: :array, required: true,
15
+ desc: "Portals to scaffold tests for (e.g. admin,org)"
16
+ class_option :concerns, type: :array, default: %w[crud policy definition],
17
+ desc: "Concerns to include (crud,policy,definition,nested,model,interaction)"
18
+ class_option :parent, type: :string, desc: "Parent association for nested resources"
19
+ class_option :dest, type: :string, default: "main_app",
20
+ desc: "main_app or package name"
21
+
22
+ def scaffold
23
+ options[:portals].each { |portal| scaffold_for_portal(portal) }
24
+ end
25
+
26
+ private
27
+
28
+ CONCERN_MAP = {
29
+ "crud" => "ResourceCrud",
30
+ "policy" => "ResourcePolicy",
31
+ "definition" => "ResourceDefinition",
32
+ "model" => "ResourceModel",
33
+ "interaction" => "ResourceInteraction",
34
+ "nested" => "NestedResource",
35
+ "portal_access" => "PortalAccess"
36
+ }.freeze
37
+
38
+ def concern_module_name(concern)
39
+ CONCERN_MAP.fetch(concern) { concern.camelize }
40
+ end
41
+
42
+ def scaffold_for_portal(portal)
43
+ @portal = portal
44
+ @resource_class = name
45
+ @file_name = name.underscore.tr("/", "_")
46
+ @class_name = "#{portal.camelize}Portal::#{name.tr("::", "")}Test"
47
+ @concerns = options[:concerns]
48
+ @parent = options[:parent]
49
+ target_dir = (options[:dest] == "main_app") ? "test/integration" : "packages/#{options[:dest]}/test/integration"
50
+ target = "#{target_dir}/#{portal}_portal/#{@file_name}_test.rb"
51
+ template "integration_test.rb.tt", target
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class <%= @class_name %> < ActionDispatch::IntegrationTest
6
+ <% @concerns.each do |c| -%>
7
+ include Plutonium::Testing::<%= concern_module_name(c) %>
8
+ <% end -%>
9
+
10
+ resource_tests_for <%= @resource_class %>,
11
+ portal: :<%= @portal %><% if @parent %>,
12
+ parent: :<%= @parent %><% end %>
13
+
14
+ setup do
15
+ # TODO: replace with your factories.
16
+ @account = nil
17
+ login_as(@account)
18
+ end
19
+
20
+ <% if @concerns.include?("crud") -%>
21
+ def create_resource!
22
+ <%= @resource_class %>.create!(
23
+ # TODO: fill in valid attributes
24
+ )
25
+ end
26
+
27
+ def valid_create_params
28
+ {} # TODO
29
+ end
30
+
31
+ def valid_update_params
32
+ {} # TODO
33
+ end
34
+ <% end -%>
35
+ <% if @concerns.include?("policy") -%>
36
+
37
+ def policy_roles
38
+ {<%= @portal %>: -> { @account }}
39
+ end
40
+
41
+ def policy_record
42
+ create_resource!
43
+ end
44
+
45
+ def policy_matrix
46
+ {
47
+ index: %i[<%= @portal %>],
48
+ show: %i[<%= @portal %>],
49
+ create: %i[<%= @portal %>],
50
+ update: %i[<%= @portal %>],
51
+ destroy: %i[<%= @portal %>]
52
+ }
53
+ end
54
+ <% end -%>
55
+ <% if @concerns.include?("nested") && @parent -%>
56
+
57
+ def parent_record!
58
+ # TODO: return the current tenant for this test
59
+ end
60
+
61
+ def other_parent_record!
62
+ # TODO: return a sibling tenant
63
+ end
64
+ <% end -%>
65
+ end
@@ -31,7 +31,8 @@ module Plutonium
31
31
  #
32
32
  # @return [String, nil] The confirmation message or nil if not applicable
33
33
  def confirmation
34
- super || (@immediate ? "#{label}?" : nil)
34
+ return @confirmation unless @confirmation.nil?
35
+ @immediate ? "#{label}?" : nil
35
36
  end
36
37
 
37
38
  # Factory for creating Interactive actions