plutonium 0.45.3 → 0.47.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 +150 -0
- data/.claude/skills/plutonium-assets/SKILL.md +248 -157
- data/.claude/skills/{plutonium-rodauth → plutonium-auth}/SKILL.md +195 -229
- data/.claude/skills/plutonium-controller/SKILL.md +9 -2
- data/.claude/skills/plutonium-create-resource/SKILL.md +22 -1
- data/.claude/skills/plutonium-definition/SKILL.md +521 -7
- data/.claude/skills/plutonium-entity-scoping/SKILL.md +317 -0
- data/.claude/skills/plutonium-forms/SKILL.md +8 -1
- data/.claude/skills/plutonium-installation/SKILL.md +25 -2
- data/.claude/skills/plutonium-interaction/SKILL.md +32 -2
- data/.claude/skills/plutonium-invites/SKILL.md +11 -7
- data/.claude/skills/plutonium-model/SKILL.md +50 -50
- data/.claude/skills/plutonium-nested-resources/SKILL.md +18 -1
- data/.claude/skills/plutonium-package/SKILL.md +8 -1
- data/.claude/skills/plutonium-policy/SKILL.md +69 -78
- data/.claude/skills/plutonium-portal/SKILL.md +26 -70
- data/.claude/skills/plutonium-testing/SKILL.md +268 -0
- data/.claude/skills/plutonium-views/SKILL.md +9 -2
- data/.yarnrc.yml +1 -0
- data/CHANGELOG.md +38 -0
- data/app/assets/plutonium.css +1 -1
- data/app/views/rodauth/_login_form.html.erb +0 -3
- data/app/views/rodauth/confirm_password.html.erb +0 -4
- data/app/views/rodauth/create_account.html.erb +0 -3
- data/app/views/rodauth/logout.html.erb +0 -3
- 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-08-plutonium-skills-overhaul.md +481 -0
- 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-08-plutonium-skills-overhaul-design.md +236 -0
- data/docs/superpowers/specs/2026-04-14-plutonium-testing-design.md +364 -0
- data/gemfiles/rails_7.gemfile.lock +1 -1
- data/gemfiles/rails_8.0.gemfile.lock +1 -1
- data/gemfiles/rails_8.1.gemfile.lock +1 -1
- data/lib/generators/pu/core/update/update_generator.rb +8 -0
- data/lib/generators/pu/gem/active_shrine/active_shrine_generator.rb +56 -0
- data/lib/generators/pu/invites/install_generator.rb +8 -1
- data/lib/generators/pu/lib/plutonium_generators/concerns/actions.rb +43 -0
- data/lib/generators/pu/profile/concerns/profile_arguments.rb +10 -4
- data/lib/generators/pu/profile/conn_generator.rb +9 -12
- data/lib/generators/pu/profile/install_generator.rb +5 -2
- data/lib/generators/pu/rodauth/templates/app/rodauth/account_rodauth_plugin.rb.tt +3 -0
- data/lib/generators/pu/saas/portal_generator.rb +4 -9
- data/lib/generators/pu/saas/welcome/templates/app/views/welcome/onboarding.html.erb.tt +2 -2
- 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/core/controller.rb +18 -1
- data/lib/plutonium/engine.rb +18 -5
- 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/layout/rodauth_layout.rb +6 -1
- data/lib/plutonium/version.rb +1 -1
- data/lib/plutonium.rb +2 -0
- data/package.json +1 -1
- data/yarn.lock +6037 -3893
- metadata +27 -8
- data/.claude/skills/plutonium/skill.md +0 -130
- data/.claude/skills/plutonium-definition-actions/SKILL.md +0 -424
- data/.claude/skills/plutonium-definition-query/SKILL.md +0 -364
- data/.claude/skills/plutonium-profile/SKILL.md +0 -276
- data/.claude/skills/plutonium-theming/SKILL.md +0 -424
|
@@ -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,236 @@
|
|
|
1
|
+
# Plutonium Skills Overhaul — Design
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-04-08
|
|
4
|
+
**Status:** Approved (pending user review of this doc)
|
|
5
|
+
|
|
6
|
+
## Problem
|
|
7
|
+
|
|
8
|
+
The current Plutonium skills (`.claude/skills/plutonium-*`) are content-correct but
|
|
9
|
+
fail to trigger at the right moments. Concrete failure modes observed in real
|
|
10
|
+
sessions:
|
|
11
|
+
|
|
12
|
+
1. Agents write `relation_scope` overrides without ever loading `plutonium-policy`,
|
|
13
|
+
bypassing `default_relation_scope` and breaking entity scoping.
|
|
14
|
+
2. Agents hand-write resource files instead of using `pu:res:scaffold`.
|
|
15
|
+
3. Agents miss cross-cutting concerns (multi-tenancy spans 4 skills; nobody reads 4).
|
|
16
|
+
4. Greenfield onboarding is one-skill-at-a-time as the agent stumbles into mistakes.
|
|
17
|
+
5. The definition concept is split across 3 skills — agents read one and miss the others.
|
|
18
|
+
|
|
19
|
+
Root cause: skill `description:` fields list **topics** ("authorization, attribute
|
|
20
|
+
visibility, relation scoping..."). Agents triage descriptions against **moments**
|
|
21
|
+
("I am about to write `relation_scope`"). Topic-based descriptions don't fire.
|
|
22
|
+
|
|
23
|
+
## Goals
|
|
24
|
+
|
|
25
|
+
1. Skills get invoked at the *moment* an agent is about to make a relevant decision.
|
|
26
|
+
2. The most expensive mistakes live in a fixed 🚨 block at the top of the relevant skill.
|
|
27
|
+
3. Cross-cutting concerns (entity scoping especially) are consolidated and discoverable.
|
|
28
|
+
4. **Generators are the default path.** Every skill that touches a generator-backed
|
|
29
|
+
concern leads with "use `pu:...`, do not hand-write."
|
|
30
|
+
5. **Greenfield onboarding works.** When an agent starts new work, it loads a bundle
|
|
31
|
+
of foundational skills upfront via the index skill.
|
|
32
|
+
6. The index skill (`plutonium`) acts as a router AND a greenfield bootstrapper.
|
|
33
|
+
7. Critical workflows are checklists; explanation is prose.
|
|
34
|
+
|
|
35
|
+
## Non-goals
|
|
36
|
+
|
|
37
|
+
- Not rewriting prose content wholesale — content is mostly correct.
|
|
38
|
+
- No telemetry/measurement infrastructure.
|
|
39
|
+
- No new code examples beyond what's needed for the 🚨 blocks.
|
|
40
|
+
- No skill renames beyond `plutonium-rodauth → plutonium-auth`.
|
|
41
|
+
|
|
42
|
+
## Final skill set (20 → 17)
|
|
43
|
+
|
|
44
|
+
### Merges
|
|
45
|
+
|
|
46
|
+
| New skill | Absorbs | Rationale |
|
|
47
|
+
|---|---|---|
|
|
48
|
+
| `plutonium-definition` | + `plutonium-definition-actions`, `plutonium-definition-query` | Agents writing a definition need all three; today's split causes "read one, miss two" |
|
|
49
|
+
| `plutonium-auth` (renamed from `plutonium-rodauth`) | + `plutonium-profile` | Profile is a thin layer over rodauth, almost never edited independently |
|
|
50
|
+
| `plutonium-assets` | + `plutonium-theming` | Both are "configure the frontend toolchain" — Tailwind + Stimulus + tokens are one mental model |
|
|
51
|
+
|
|
52
|
+
### New skill
|
|
53
|
+
|
|
54
|
+
| Skill | Purpose |
|
|
55
|
+
|---|---|
|
|
56
|
+
| `plutonium-entity-scoping` | Consolidates the entity-scoping/multi-tenancy-specific content currently fragmented across `plutonium-model`, `plutonium-policy`, `plutonium-portal`, and `plutonium-invites`. The single source of truth for: `associated_with` resolution, `default_relation_scope` rules, `relation_scope` override safety, entity strategies (path/custom), and the join-table/grandchild model shapes. |
|
|
57
|
+
|
|
58
|
+
The four source skills retain their general content but defer to
|
|
59
|
+
`plutonium-entity-scoping` for tenancy specifics via cross-reference.
|
|
60
|
+
|
|
61
|
+
### Stays separate
|
|
62
|
+
|
|
63
|
+
`plutonium`, `plutonium-installation`, `plutonium-create-resource`, `plutonium-model`,
|
|
64
|
+
`plutonium-policy`, `plutonium-controller`, `plutonium-interaction`, `plutonium-portal`,
|
|
65
|
+
`plutonium-package`, `plutonium-nested-resources`, `plutonium-invites`,
|
|
66
|
+
`plutonium-forms`, `plutonium-views`.
|
|
67
|
+
|
|
68
|
+
**Final count: 17 skills** (20 − 3 merges + 1 new − 1 rename effect = 17).
|
|
69
|
+
|
|
70
|
+
## Skill template
|
|
71
|
+
|
|
72
|
+
Every skill follows this fixed shape so agents always know where to look:
|
|
73
|
+
|
|
74
|
+
```markdown
|
|
75
|
+
---
|
|
76
|
+
name: plutonium-<topic>
|
|
77
|
+
description: Use BEFORE <specific verb/construct>. Also when <secondary trigger>. <one-line scope>.
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
# plutonium-<topic>
|
|
81
|
+
|
|
82
|
+
## 🚨 Critical (read first)
|
|
83
|
+
- **Use generators, not hand-written files.** `pu:<gen>` — never create <X> manually.
|
|
84
|
+
- **<Top anti-pattern #1>** — one-liner + why.
|
|
85
|
+
- **<Top anti-pattern #2>** — one-liner + why.
|
|
86
|
+
- **Related skills you may also need:** [list with one-line reasons]
|
|
87
|
+
|
|
88
|
+
## When to use this skill
|
|
89
|
+
Checklist of decision points / code constructs that should trigger loading this skill.
|
|
90
|
+
|
|
91
|
+
## Quick checklist (bootstrap + high-traffic skills only)
|
|
92
|
+
Numbered checklist for the most common workflow. Agent can TaskCreate from this.
|
|
93
|
+
|
|
94
|
+
## <Sections — scaled to topic>
|
|
95
|
+
Prose + code examples.
|
|
96
|
+
|
|
97
|
+
## Gotchas
|
|
98
|
+
The full anti-pattern list with explanations.
|
|
99
|
+
|
|
100
|
+
## See also
|
|
101
|
+
Cross-references to related skills.
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Template rules
|
|
105
|
+
|
|
106
|
+
- **Description starts with `Use BEFORE <verb/construct>`** — verbs and code names,
|
|
107
|
+
not topic nouns. This is the single biggest triggering fix.
|
|
108
|
+
- **🚨 Critical block is fixed-position** (always right after the H1) and capped at
|
|
109
|
+
~5 bullets.
|
|
110
|
+
- **Generator-first** is in the 🚨 block of every skill where a generator exists.
|
|
111
|
+
- **Cross-references in 🚨 are mandatory** — the "Related skills" bullet replaces
|
|
112
|
+
most of the multi-tenancy discoverability problem.
|
|
113
|
+
- **TOC at top of merged skills** so agents can jump to a section without reading
|
|
114
|
+
the whole file.
|
|
115
|
+
|
|
116
|
+
## The index skill (`plutonium`) as router + bootstrapper
|
|
117
|
+
|
|
118
|
+
```markdown
|
|
119
|
+
---
|
|
120
|
+
name: plutonium
|
|
121
|
+
description: Use BEFORE starting any Plutonium work — new app, new feature, or first edit in an unfamiliar area. Routes you to the right skills and bootstraps greenfield work.
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
# plutonium
|
|
125
|
+
|
|
126
|
+
## 🚨 Read this first
|
|
127
|
+
- Plutonium is generator-driven. Almost every file you'd hand-write has a `pu:*`
|
|
128
|
+
generator. Use it. Hand-written files drift from conventions.
|
|
129
|
+
- For greenfield (new app or substantial new feature), load the **bootstrap bundle**
|
|
130
|
+
below before writing any code.
|
|
131
|
+
- For targeted edits, use the **router table**.
|
|
132
|
+
- For anything touching tenant scoping, load `plutonium-entity-scoping`.
|
|
133
|
+
|
|
134
|
+
## Greenfield bootstrap bundle
|
|
135
|
+
Triggers: installing Plutonium, adding the first resource of a new domain, building
|
|
136
|
+
a new portal/package, "set up X from scratch", "build me a Y app".
|
|
137
|
+
|
|
138
|
+
Load ALL of these before writing code:
|
|
139
|
+
1. `plutonium-installation`
|
|
140
|
+
2. `plutonium-create-resource`
|
|
141
|
+
3. `plutonium-model`
|
|
142
|
+
4. `plutonium-policy`
|
|
143
|
+
5. `plutonium-entity-scoping` ← new
|
|
144
|
+
6. `plutonium-portal`
|
|
145
|
+
7. `plutonium-definition`
|
|
146
|
+
|
|
147
|
+
## Router (targeted edits)
|
|
148
|
+
| About to... | Load |
|
|
149
|
+
|---|---|
|
|
150
|
+
| Write/edit a model, add associations | `plutonium-model` |
|
|
151
|
+
| Scope a model to a tenant, write `associated_with`, deal with multi-tenancy | `plutonium-entity-scoping` |
|
|
152
|
+
| Write `relation_scope`, `permitted_attributes`, override a policy | `plutonium-policy` (+ `plutonium-entity-scoping` if scoping) |
|
|
153
|
+
| Add fields, search, filters, custom actions to a resource | `plutonium-definition` |
|
|
154
|
+
| Customize a controller action, hook, redirect | `plutonium-controller` |
|
|
155
|
+
| Encapsulate business logic, multi-step ops | `plutonium-interaction` |
|
|
156
|
+
| Build a custom page, panel, table, layout | `plutonium-views` |
|
|
157
|
+
| Customize forms, field builders, inputs | `plutonium-forms` |
|
|
158
|
+
| Configure Tailwind, Stimulus, design tokens | `plutonium-assets` |
|
|
159
|
+
| Set up Rodauth, accounts, profile pages | `plutonium-auth` |
|
|
160
|
+
| Set up user invitations / membership | `plutonium-invites` (+ `plutonium-entity-scoping`) |
|
|
161
|
+
| Configure parent/child resources, nested routes | `plutonium-nested-resources` |
|
|
162
|
+
| Create a portal or feature package | `plutonium-portal` / `plutonium-package` |
|
|
163
|
+
|
|
164
|
+
## Generator catalog
|
|
165
|
+
[Table of `pu:*` generators with one-line purpose + which skill covers it.]
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Execution phases
|
|
169
|
+
|
|
170
|
+
### Phase A — Restructure (mechanical)
|
|
171
|
+
1. Merge `plutonium-definition-actions` + `plutonium-definition-query` into
|
|
172
|
+
`plutonium-definition` with TOC + sections.
|
|
173
|
+
2. Rename `plutonium-rodauth` → `plutonium-auth`; merge `plutonium-profile` into it.
|
|
174
|
+
3. Merge `plutonium-theming` into `plutonium-assets` with TOC + sections.
|
|
175
|
+
4. Delete merged source skills.
|
|
176
|
+
5. Grep the codebase for references to deleted/renamed skill names; update.
|
|
177
|
+
|
|
178
|
+
### Phase B — Create `plutonium-entity-scoping`
|
|
179
|
+
- Extract entity-scoping content from `plutonium-model`, `plutonium-policy`,
|
|
180
|
+
`plutonium-portal`, `plutonium-invites`.
|
|
181
|
+
- Single source of truth for: `associated_with`, `default_relation_scope`,
|
|
182
|
+
`relation_scope` override safety, entity strategies, and the three model shapes
|
|
183
|
+
(direct child, join-table, grandchild) with worked examples.
|
|
184
|
+
- Source skills keep general content but link here for tenancy specifics.
|
|
185
|
+
|
|
186
|
+
### Phase C — Rewrite descriptions (every skill)
|
|
187
|
+
- Format: `Use BEFORE <verb/construct>. Also when <secondary>. <scope>.`
|
|
188
|
+
- Each calls out specific code constructs.
|
|
189
|
+
|
|
190
|
+
### Phase D — Add 🚨 Critical block to every skill
|
|
191
|
+
- Fixed position (right after H1).
|
|
192
|
+
- ~5 bullets max.
|
|
193
|
+
- Pull existing anti-patterns from gotchas to the top.
|
|
194
|
+
|
|
195
|
+
### Phase E — Rewrite `plutonium` index skill
|
|
196
|
+
- Bootstrap bundle.
|
|
197
|
+
- Router table.
|
|
198
|
+
- Generator catalog.
|
|
199
|
+
|
|
200
|
+
### Phase F — Quick checklists
|
|
201
|
+
Add to bootstrap-bundle skills + `plutonium-definition` + `plutonium-entity-scoping`.
|
|
202
|
+
Skip for low-traffic skills.
|
|
203
|
+
|
|
204
|
+
### Phase G — Cross-references
|
|
205
|
+
- Every tenancy-touching skill links to `plutonium-entity-scoping`.
|
|
206
|
+
- Definition / policy / model get mutual cross-refs.
|
|
207
|
+
- Verify bidirectionality.
|
|
208
|
+
|
|
209
|
+
## Risks & mitigations
|
|
210
|
+
|
|
211
|
+
| Risk | Mitigation |
|
|
212
|
+
|---|---|
|
|
213
|
+
| Merged skills are bigger → more tokens per load | TOC at top, section anchors, router tells agent which section to jump to |
|
|
214
|
+
| Renaming `plutonium-rodauth → plutonium-auth` breaks references | Grep + update in Phase A step 5; also update CLAUDE.md if mentioned |
|
|
215
|
+
| Description rewrites are subjective | Same pattern for all (`Use BEFORE <verb>`), reviewed for consistency |
|
|
216
|
+
| `plutonium-entity-scoping` could become a dumping ground | Strict scope: only entity scoping itself. General model/policy stuff stays in source skills. |
|
|
217
|
+
| Source skills' tenancy sections become stubs that drift | Rule: source skill has a 🚨 bullet "for entity scoping, see `plutonium-entity-scoping`" and a one-paragraph teaser, nothing more |
|
|
218
|
+
|
|
219
|
+
## Out of scope
|
|
220
|
+
|
|
221
|
+
- New code examples beyond 🚨 blocks and the three model-shape examples in
|
|
222
|
+
`plutonium-entity-scoping`.
|
|
223
|
+
- Telemetry / measurement.
|
|
224
|
+
- Skill content rewrites beyond what's needed for the new structure.
|
|
225
|
+
- Consolidating below 17 skills.
|
|
226
|
+
|
|
227
|
+
## Success criteria
|
|
228
|
+
|
|
229
|
+
1. An agent about to write `relation_scope` loads `plutonium-policy` AND
|
|
230
|
+
`plutonium-entity-scoping` from description triggering alone.
|
|
231
|
+
2. An agent doing greenfield work loads the bootstrap bundle from a single read of
|
|
232
|
+
`plutonium`.
|
|
233
|
+
3. Every skill that has a generator mentions "use the generator" in its 🚨 block.
|
|
234
|
+
4. The three model shapes (direct child / join table / grandchild) have worked
|
|
235
|
+
examples in `plutonium-entity-scoping`.
|
|
236
|
+
5. No skill description starts with a topic noun list; all start with `Use BEFORE`.
|
|
@@ -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.
|
|
@@ -12,12 +12,20 @@ module Pu
|
|
|
12
12
|
def start
|
|
13
13
|
update_gem
|
|
14
14
|
update_npm_package
|
|
15
|
+
sync_skills_if_present
|
|
15
16
|
rescue => e
|
|
16
17
|
exception "#{self.class} failed:", e
|
|
17
18
|
end
|
|
18
19
|
|
|
19
20
|
private
|
|
20
21
|
|
|
22
|
+
def sync_skills_if_present
|
|
23
|
+
return unless File.file?(Rails.root.join(".claude", "skills", "plutonium", "SKILL.md"))
|
|
24
|
+
|
|
25
|
+
say_status :update, "Syncing Plutonium Claude skills...", :green
|
|
26
|
+
Rails::Generators.invoke("pu:skills:sync", [], destination_root: Rails.root)
|
|
27
|
+
end
|
|
28
|
+
|
|
21
29
|
def update_gem
|
|
22
30
|
say_status :update, "Updating plutonium gem...", :green
|
|
23
31
|
run "bundle update plutonium"
|