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.
- checksums.yaml +4 -4
- data/.claude/skills/plutonium/SKILL.md +4 -0
- data/.claude/skills/plutonium-interaction/SKILL.md +23 -0
- data/.claude/skills/plutonium-nested-resources/SKILL.md +10 -0
- data/.claude/skills/plutonium-testing/SKILL.md +268 -0
- data/.yarnrc.yml +1 -0
- data/CHANGELOG.md +23 -0
- data/Rakefile +10 -1
- data/app/assets/plutonium.css +1 -1
- data/docs/.vitepress/config.ts +6 -0
- data/docs/guides/nested-resources.md +10 -0
- data/docs/guides/testing.md +154 -0
- data/docs/reference/controller/index.md +9 -4
- data/docs/superpowers/plans/2026-04-14-plutonium-testing.md +2046 -0
- data/docs/superpowers/plans/2026-04-14-plutonium-testing.md.tasks.json +21 -0
- data/docs/superpowers/specs/2026-04-14-plutonium-testing-design.md +364 -0
- data/gemfiles/rails_8.1.gemfile.lock +27 -1
- data/lib/generators/pu/test/install/install_generator.rb +34 -0
- data/lib/generators/pu/test/install/templates/plutonium_testing.rb.tt +14 -0
- data/lib/generators/pu/test/scaffold/scaffold_generator.rb +55 -0
- data/lib/generators/pu/test/scaffold/templates/integration_test.rb.tt +65 -0
- data/lib/plutonium/action/interactive.rb +2 -1
- data/lib/plutonium/core/controller.rb +18 -1
- data/lib/plutonium/helpers/turbo_stream_actions_helper.rb +20 -1
- data/lib/plutonium/testing/auth_helpers.rb +62 -0
- data/lib/plutonium/testing/dsl.rb +73 -0
- data/lib/plutonium/testing/nested_resource.rb +58 -0
- data/lib/plutonium/testing/portal_access.rb +49 -0
- data/lib/plutonium/testing/resource_crud.rb +104 -0
- data/lib/plutonium/testing/resource_definition.rb +61 -0
- data/lib/plutonium/testing/resource_interaction.rb +51 -0
- data/lib/plutonium/testing/resource_model.rb +53 -0
- data/lib/plutonium/testing/resource_policy.rb +72 -0
- data/lib/plutonium/testing.rb +16 -0
- data/lib/plutonium/ui/action_button.rb +1 -1
- data/lib/plutonium/version.rb +1 -1
- data/lib/plutonium.rb +2 -0
- data/package.json +1 -1
- data/plutonium.gemspec +2 -0
- data/yarn.lock +6037 -3893
- 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.
|
|
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
|
-
|
|
34
|
+
return @confirmation unless @confirmation.nil?
|
|
35
|
+
@immediate ? "#{label}?" : nil
|
|
35
36
|
end
|
|
36
37
|
|
|
37
38
|
# Factory for creating Interactive actions
|