agent-notes 2.0.4__py3-none-any.whl
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.
- agent_notes/VERSION +1 -0
- agent_notes/__init__.py +1 -0
- agent_notes/__main__.py +4 -0
- agent_notes/cli.py +348 -0
- agent_notes/commands/__init__.py +27 -0
- agent_notes/commands/_install_helpers.py +262 -0
- agent_notes/commands/build.py +170 -0
- agent_notes/commands/doctor.py +112 -0
- agent_notes/commands/info.py +95 -0
- agent_notes/commands/install.py +99 -0
- agent_notes/commands/list.py +169 -0
- agent_notes/commands/memory.py +430 -0
- agent_notes/commands/regenerate.py +152 -0
- agent_notes/commands/set_role.py +143 -0
- agent_notes/commands/uninstall.py +26 -0
- agent_notes/commands/update.py +169 -0
- agent_notes/commands/validate.py +199 -0
- agent_notes/commands/wizard.py +720 -0
- agent_notes/config.py +154 -0
- agent_notes/data/agents/agents.yaml +352 -0
- agent_notes/data/agents/analyst.md +45 -0
- agent_notes/data/agents/api-reviewer.md +47 -0
- agent_notes/data/agents/architect.md +46 -0
- agent_notes/data/agents/coder.md +28 -0
- agent_notes/data/agents/database-specialist.md +45 -0
- agent_notes/data/agents/debugger.md +47 -0
- agent_notes/data/agents/devil.md +47 -0
- agent_notes/data/agents/devops.md +38 -0
- agent_notes/data/agents/explorer.md +23 -0
- agent_notes/data/agents/integrations.md +44 -0
- agent_notes/data/agents/lead.md +216 -0
- agent_notes/data/agents/performance-profiler.md +44 -0
- agent_notes/data/agents/refactorer.md +48 -0
- agent_notes/data/agents/reviewer.md +44 -0
- agent_notes/data/agents/security-auditor.md +44 -0
- agent_notes/data/agents/system-auditor.md +38 -0
- agent_notes/data/agents/tech-writer.md +32 -0
- agent_notes/data/agents/test-runner.md +36 -0
- agent_notes/data/agents/test-writer.md +39 -0
- agent_notes/data/cli/claude.yaml +25 -0
- agent_notes/data/cli/copilot.yaml +18 -0
- agent_notes/data/cli/opencode.yaml +22 -0
- agent_notes/data/commands/brainstorm.md +8 -0
- agent_notes/data/commands/debug.md +9 -0
- agent_notes/data/commands/review.md +10 -0
- agent_notes/data/global-claude.md +290 -0
- agent_notes/data/global-copilot.md +27 -0
- agent_notes/data/global-opencode.md +40 -0
- agent_notes/data/hooks/session-context.md.tpl +19 -0
- agent_notes/data/models/claude-haiku-4-5.yaml +15 -0
- agent_notes/data/models/claude-opus-4-1.yaml +16 -0
- agent_notes/data/models/claude-opus-4-5.yaml +16 -0
- agent_notes/data/models/claude-opus-4-6.yaml +16 -0
- agent_notes/data/models/claude-opus-4-7.yaml +15 -0
- agent_notes/data/models/claude-sonnet-4-5.yaml +16 -0
- agent_notes/data/models/claude-sonnet-4-6.yaml +15 -0
- agent_notes/data/models/claude-sonnet-4.yaml +16 -0
- agent_notes/data/pricing.yaml +33 -0
- agent_notes/data/roles/orchestrator.yaml +5 -0
- agent_notes/data/roles/reasoner.yaml +5 -0
- agent_notes/data/roles/scout.yaml +5 -0
- agent_notes/data/roles/worker.yaml +5 -0
- agent_notes/data/rules/code-quality.md +9 -0
- agent_notes/data/rules/safety.md +10 -0
- agent_notes/data/scripts/cost-report +211 -0
- agent_notes/data/skills/brainstorming/SKILL.md +57 -0
- agent_notes/data/skills/code-review/SKILL.md +64 -0
- agent_notes/data/skills/debugging-protocol/SKILL.md +51 -0
- agent_notes/data/skills/docker-compose/SKILL.md +318 -0
- agent_notes/data/skills/docker-compose-advanced/SKILL.md +575 -0
- agent_notes/data/skills/docker-dockerfile/SKILL.md +385 -0
- agent_notes/data/skills/docker-dockerfile-languages/SKILL.md +293 -0
- agent_notes/data/skills/git/SKILL.md +87 -0
- agent_notes/data/skills/rails-active-storage/SKILL.md +321 -0
- agent_notes/data/skills/rails-broadcasting/SKILL.md +374 -0
- agent_notes/data/skills/rails-concerns/SKILL.md +806 -0
- agent_notes/data/skills/rails-controllers/SKILL.md +510 -0
- agent_notes/data/skills/rails-controllers-advanced/SKILL.md +441 -0
- agent_notes/data/skills/rails-helpers/SKILL.md +677 -0
- agent_notes/data/skills/rails-initializers/SKILL.md +79 -0
- agent_notes/data/skills/rails-javascript/SKILL.md +567 -0
- agent_notes/data/skills/rails-jobs/SKILL.md +700 -0
- agent_notes/data/skills/rails-kamal/SKILL.md +483 -0
- agent_notes/data/skills/rails-lib/SKILL.md +101 -0
- agent_notes/data/skills/rails-mailers/SKILL.md +321 -0
- agent_notes/data/skills/rails-migrations/SKILL.md +268 -0
- agent_notes/data/skills/rails-models/SKILL.md +459 -0
- agent_notes/data/skills/rails-models-advanced/SKILL.md +398 -0
- agent_notes/data/skills/rails-routes/SKILL.md +804 -0
- agent_notes/data/skills/rails-style/SKILL.md +538 -0
- agent_notes/data/skills/rails-testing-controllers/SKILL.md +343 -0
- agent_notes/data/skills/rails-testing-models/SKILL.md +296 -0
- agent_notes/data/skills/rails-testing-system/SKILL.md +375 -0
- agent_notes/data/skills/rails-validations/SKILL.md +108 -0
- agent_notes/data/skills/rails-view-components/SKILL.md +511 -0
- agent_notes/data/skills/rails-view-components-advanced/SKILL.md +376 -0
- agent_notes/data/skills/rails-views/SKILL.md +413 -0
- agent_notes/data/skills/rails-views-advanced/SKILL.md +450 -0
- agent_notes/data/skills/refactoring-protocol/SKILL.md +64 -0
- agent_notes/data/skills/tdd/SKILL.md +57 -0
- agent_notes/data/templates/__init__.py +1 -0
- agent_notes/data/templates/__pycache__/__init__.cpython-314.pyc +0 -0
- agent_notes/data/templates/frontmatter/__init__.py +1 -0
- agent_notes/data/templates/frontmatter/__pycache__/__init__.cpython-314.pyc +0 -0
- agent_notes/data/templates/frontmatter/__pycache__/claude.cpython-314.pyc +0 -0
- agent_notes/data/templates/frontmatter/__pycache__/cursor.cpython-314.pyc +0 -0
- agent_notes/data/templates/frontmatter/__pycache__/opencode.cpython-314.pyc +0 -0
- agent_notes/data/templates/frontmatter/claude.py +44 -0
- agent_notes/data/templates/frontmatter/opencode.py +104 -0
- agent_notes/doctor_checks.py +189 -0
- agent_notes/domain/__init__.py +17 -0
- agent_notes/domain/agent.py +34 -0
- agent_notes/domain/cli_backend.py +40 -0
- agent_notes/domain/diagnostics.py +29 -0
- agent_notes/domain/diff.py +44 -0
- agent_notes/domain/model.py +27 -0
- agent_notes/domain/role.py +13 -0
- agent_notes/domain/rule.py +13 -0
- agent_notes/domain/skill.py +15 -0
- agent_notes/domain/state.py +46 -0
- agent_notes/install_state.py +11 -0
- agent_notes/registries/__init__.py +16 -0
- agent_notes/registries/_base.py +46 -0
- agent_notes/registries/agent_registry.py +107 -0
- agent_notes/registries/cli_registry.py +89 -0
- agent_notes/registries/model_registry.py +85 -0
- agent_notes/registries/role_registry.py +64 -0
- agent_notes/registries/rule_registry.py +80 -0
- agent_notes/registries/skill_registry.py +141 -0
- agent_notes/services/__init__.py +8 -0
- agent_notes/services/diagnostics/__init__.py +47 -0
- agent_notes/services/diagnostics/_checks.py +272 -0
- agent_notes/services/diagnostics/_display.py +346 -0
- agent_notes/services/diagnostics/_fix.py +169 -0
- agent_notes/services/diff.py +349 -0
- agent_notes/services/fs.py +195 -0
- agent_notes/services/install_state_builder.py +210 -0
- agent_notes/services/installer.py +293 -0
- agent_notes/services/memory_backend.py +155 -0
- agent_notes/services/rendering.py +329 -0
- agent_notes/services/session_context.py +23 -0
- agent_notes/services/settings_writer.py +79 -0
- agent_notes/services/state_store.py +249 -0
- agent_notes/services/ui.py +419 -0
- agent_notes/services/user_config.py +62 -0
- agent_notes/services/validation.py +67 -0
- agent_notes/state.py +21 -0
- agent_notes-2.0.4.dist-info/METADATA +14 -0
- agent_notes-2.0.4.dist-info/RECORD +162 -0
- agent_notes-2.0.4.dist-info/WHEEL +5 -0
- agent_notes-2.0.4.dist-info/entry_points.txt +2 -0
- agent_notes-2.0.4.dist-info/licenses/LICENSE +21 -0
- agent_notes-2.0.4.dist-info/top_level.txt +2 -0
- tests/conftest.py +20 -0
- tests/functional/__init__.py +0 -0
- tests/functional/test_build_commands.py +88 -0
- tests/functional/test_registries.py +128 -0
- tests/integration/__init__.py +0 -0
- tests/integration/test_build_output.py +129 -0
- tests/plugins/__init__.py +0 -0
- tests/plugins/test_agents.py +93 -0
- tests/plugins/test_skills.py +77 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rails-testing-system
|
|
3
|
+
description: "Rails testing: helper tests, mailer tests, job tests, fixtures, test helpers, and tips"
|
|
4
|
+
group: rails
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Testing Helpers, Jobs & Configuration
|
|
8
|
+
|
|
9
|
+
## Helper Tests
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
class CardsHelperTest < ActionView::TestCase
|
|
13
|
+
test "card_article_tag generates article with classes" do
|
|
14
|
+
card = cards(:logo)
|
|
15
|
+
card.stub(:golden?, true) do
|
|
16
|
+
result = card_article_tag(card) { "Content" }
|
|
17
|
+
|
|
18
|
+
assert_match /golden-effect/, result
|
|
19
|
+
assert_match /article/, result
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
test "card_status_badge returns correct badge" do
|
|
24
|
+
card = cards(:logo)
|
|
25
|
+
|
|
26
|
+
badge = card_status_badge(card)
|
|
27
|
+
|
|
28
|
+
assert_match /badge/, badge
|
|
29
|
+
assert_match /published/, badge
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Mailer Tests
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
class NotificationMailerTest < ActionMailer::TestCase
|
|
40
|
+
test "notification email" do
|
|
41
|
+
user = users(:david)
|
|
42
|
+
card = cards(:logo)
|
|
43
|
+
|
|
44
|
+
email = NotificationMailer.card_assigned(user, card)
|
|
45
|
+
|
|
46
|
+
assert_emails 1 do
|
|
47
|
+
email.deliver_now
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
assert_equal [user.email], email.to
|
|
51
|
+
assert_equal "You've been assigned to a card", email.subject
|
|
52
|
+
assert_match card.title, email.body.encoded
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
test "email includes correct links" do
|
|
56
|
+
user = users(:david)
|
|
57
|
+
card = cards(:logo)
|
|
58
|
+
|
|
59
|
+
email = NotificationMailer.card_assigned(user, card)
|
|
60
|
+
|
|
61
|
+
assert_match card_url(card), email.body.encoded
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Job Tests
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
class NotificationJobTest < ActiveJob::TestCase
|
|
72
|
+
test "enqueues job" do
|
|
73
|
+
assert_enqueued_with(job: NotificationJob) do
|
|
74
|
+
NotificationJob.perform_later(users(:david), "test")
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
test "job performs notification" do
|
|
79
|
+
user = users(:david)
|
|
80
|
+
|
|
81
|
+
perform_enqueued_jobs do
|
|
82
|
+
NotificationJob.perform_later(user, "test")
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Assert side effects
|
|
86
|
+
assert_equal 1, user.notifications.count
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
test "job retries on error" do
|
|
90
|
+
NotificationJob.stub(:notify, -> { raise Net::HTTPServerError }) do
|
|
91
|
+
assert_enqueued_jobs 2 do # Original + 1 retry
|
|
92
|
+
perform_enqueued_jobs(only: NotificationJob) do
|
|
93
|
+
NotificationJob.perform_later(users(:david), "test")
|
|
94
|
+
rescue Net::HTTPServerError
|
|
95
|
+
# Expected
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Fixtures
|
|
106
|
+
|
|
107
|
+
### Creating Fixtures
|
|
108
|
+
|
|
109
|
+
```yaml
|
|
110
|
+
# test/fixtures/cards.yml
|
|
111
|
+
logo:
|
|
112
|
+
id: <%= ActiveRecord::FixtureSet.identify("logo", :uuid) %>
|
|
113
|
+
number: 1
|
|
114
|
+
board: writebook
|
|
115
|
+
creator: david
|
|
116
|
+
title: The logo isn't big enough
|
|
117
|
+
status: published
|
|
118
|
+
created_at: <%= 1.week.ago %>
|
|
119
|
+
account: company
|
|
120
|
+
|
|
121
|
+
draft:
|
|
122
|
+
number: 2
|
|
123
|
+
board: writebook
|
|
124
|
+
creator: david
|
|
125
|
+
title: Draft card
|
|
126
|
+
status: draft
|
|
127
|
+
created_at: <%= 1.day.ago %>
|
|
128
|
+
account: company
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Using Fixtures
|
|
132
|
+
|
|
133
|
+
```ruby
|
|
134
|
+
test "fixture data is loaded" do
|
|
135
|
+
card = cards(:logo)
|
|
136
|
+
|
|
137
|
+
assert_equal "The logo isn't big enough", card.title
|
|
138
|
+
assert_equal boards(:writebook), card.board
|
|
139
|
+
assert card.published?
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
test "fixture associations work" do
|
|
143
|
+
card = cards(:logo)
|
|
144
|
+
|
|
145
|
+
assert_equal users(:david), card.creator
|
|
146
|
+
assert_equal accounts(:company), card.account
|
|
147
|
+
end
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Test Helpers
|
|
153
|
+
|
|
154
|
+
### Session Helper
|
|
155
|
+
|
|
156
|
+
```ruby
|
|
157
|
+
# test/test_helpers/session_test_helper.rb
|
|
158
|
+
module SessionTestHelper
|
|
159
|
+
def sign_in_as(user_or_fixture_name)
|
|
160
|
+
user = user_or_fixture_name.is_a?(User) ? user_or_fixture_name : users(user_or_fixture_name)
|
|
161
|
+
|
|
162
|
+
post session_url, params: {
|
|
163
|
+
email: user.email,
|
|
164
|
+
password: "password"
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
assert_response :redirect
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def logout
|
|
171
|
+
delete session_url
|
|
172
|
+
assert_response :redirect
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def logout_and_sign_in_as(user)
|
|
176
|
+
logout
|
|
177
|
+
sign_in_as(user)
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Custom Assertions
|
|
183
|
+
|
|
184
|
+
```ruby
|
|
185
|
+
# test/test_helpers/custom_assertions.rb
|
|
186
|
+
module CustomAssertions
|
|
187
|
+
def assert_card_closed(card)
|
|
188
|
+
assert card.closed?, "Expected card to be closed"
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def assert_redirected_with_notice(path, notice)
|
|
192
|
+
assert_redirected_to path
|
|
193
|
+
assert_equal notice, flash[:notice]
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def assert_turbo_stream_action(action, target)
|
|
197
|
+
assert_match action.to_s, response.body
|
|
198
|
+
assert_match target.to_s, response.body
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Test Database
|
|
206
|
+
|
|
207
|
+
### Setup
|
|
208
|
+
|
|
209
|
+
```ruby
|
|
210
|
+
# test/test_helper.rb
|
|
211
|
+
ENV["RAILS_ENV"] ||= "test"
|
|
212
|
+
require_relative "../config/environment"
|
|
213
|
+
require "rails/test_help"
|
|
214
|
+
|
|
215
|
+
class ActiveSupport::TestCase
|
|
216
|
+
# Run tests in parallel
|
|
217
|
+
parallelize(workers: :number_of_processors)
|
|
218
|
+
|
|
219
|
+
# Setup all fixtures
|
|
220
|
+
fixtures :all
|
|
221
|
+
|
|
222
|
+
# Add custom helpers
|
|
223
|
+
include SessionTestHelper
|
|
224
|
+
include CustomAssertions
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
class ActionDispatch::IntegrationTest
|
|
228
|
+
include SessionTestHelper
|
|
229
|
+
end
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Database Cleaning
|
|
233
|
+
|
|
234
|
+
```ruby
|
|
235
|
+
# Between tests, Rails automatically:
|
|
236
|
+
# - Wraps each test in a transaction
|
|
237
|
+
# - Rolls back after each test
|
|
238
|
+
# - Ensures clean state
|
|
239
|
+
|
|
240
|
+
# For system tests (JavaScript), use truncation:
|
|
241
|
+
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
|
|
242
|
+
driven_by :selenium, using: :headless_chrome
|
|
243
|
+
|
|
244
|
+
setup do
|
|
245
|
+
# Custom setup if needed
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
teardown do
|
|
249
|
+
# Cleanup if needed
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## Testing Tips
|
|
257
|
+
|
|
258
|
+
### Time Travel
|
|
259
|
+
|
|
260
|
+
```ruby
|
|
261
|
+
test "event expires after time" do
|
|
262
|
+
event = events(:notification)
|
|
263
|
+
|
|
264
|
+
travel 2.days do
|
|
265
|
+
assert event.expired?
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
test "scheduled job runs at correct time" do
|
|
270
|
+
freeze_time do
|
|
271
|
+
ScheduledJob.perform_later(Time.current + 1.hour)
|
|
272
|
+
|
|
273
|
+
travel 1.hour do
|
|
274
|
+
perform_enqueued_jobs
|
|
275
|
+
assert_job_ran
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Stubbing Methods
|
|
282
|
+
|
|
283
|
+
```ruby
|
|
284
|
+
test "uses stubbed method" do
|
|
285
|
+
user = users(:david)
|
|
286
|
+
|
|
287
|
+
user.stub(:premium?, true) do
|
|
288
|
+
assert user.can_access_premium_features?
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Testing Errors
|
|
294
|
+
|
|
295
|
+
```ruby
|
|
296
|
+
test "raises error on invalid input" do
|
|
297
|
+
assert_raises(ActiveRecord::RecordInvalid) do
|
|
298
|
+
Card.create!(title: nil, status: :published)
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
test "rescues error and handles gracefully" do
|
|
303
|
+
card = cards(:logo)
|
|
304
|
+
|
|
305
|
+
card.stub(:close, -> { raise StandardError }) do
|
|
306
|
+
assert_nothing_raised do
|
|
307
|
+
card.safe_close # Method that rescues errors
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## Best Practices
|
|
316
|
+
|
|
317
|
+
### DO
|
|
318
|
+
|
|
319
|
+
1. **Test behavior, not implementation**
|
|
320
|
+
2. **Use descriptive test names**
|
|
321
|
+
3. **Test permissions thoroughly**
|
|
322
|
+
4. **Use fixtures for consistent data**
|
|
323
|
+
5. **Keep tests independent**
|
|
324
|
+
6. **Test happy path and edge cases**
|
|
325
|
+
7. **Use helper methods to DRY up tests**
|
|
326
|
+
8. **Run tests frequently**
|
|
327
|
+
|
|
328
|
+
### DON'T
|
|
329
|
+
|
|
330
|
+
1. **Test private methods directly**
|
|
331
|
+
2. **Test framework code**
|
|
332
|
+
3. **Create brittle tests**
|
|
333
|
+
4. **Test implementation details**
|
|
334
|
+
5. **Skip setup/teardown when needed**
|
|
335
|
+
6. **Write slow tests**
|
|
336
|
+
7. **Leave failing tests**
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## Running Tests
|
|
341
|
+
|
|
342
|
+
```bash
|
|
343
|
+
# All tests
|
|
344
|
+
bin/rails test
|
|
345
|
+
|
|
346
|
+
# Specific file
|
|
347
|
+
bin/rails test test/models/card_test.rb
|
|
348
|
+
|
|
349
|
+
# Specific test
|
|
350
|
+
bin/rails test test/models/card_test.rb:10
|
|
351
|
+
|
|
352
|
+
# System tests
|
|
353
|
+
bin/rails test:system
|
|
354
|
+
|
|
355
|
+
# With coverage
|
|
356
|
+
COVERAGE=true bin/rails test
|
|
357
|
+
|
|
358
|
+
# Parallel
|
|
359
|
+
PARALLEL_WORKERS=4 bin/rails test
|
|
360
|
+
|
|
361
|
+
# Seed for reproducibility
|
|
362
|
+
bin/rails test TESTOPTS="--seed=12345"
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
## Summary
|
|
368
|
+
|
|
369
|
+
- **Model Tests**: Associations, validations, scopes, callbacks, methods
|
|
370
|
+
- **Controller Tests**: CRUD, permissions, formats, parameters
|
|
371
|
+
- **System Tests**: Full-stack browser testing with Selenium
|
|
372
|
+
- **Integration Tests**: Multi-request workflows
|
|
373
|
+
- **Fixtures**: Consistent test data
|
|
374
|
+
- **Helpers**: DRY up common test patterns
|
|
375
|
+
- **Coverage**: Aim for high coverage of critical paths
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rails-validations
|
|
3
|
+
description: "Rails model validations: built-in, custom, and conditional validation patterns"
|
|
4
|
+
group: rails
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Validations Guide
|
|
8
|
+
|
|
9
|
+
Guide for Rails model validations.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Built-in Validations
|
|
14
|
+
|
|
15
|
+
### Presence
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
validates :title, presence: true
|
|
19
|
+
validates :email, presence: true, on: :create
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Uniqueness
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
validates :email, uniqueness: true
|
|
26
|
+
validates :number, uniqueness: { scope: :account_id }
|
|
27
|
+
validates :slug, uniqueness: { case_sensitive: false }
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Format
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }
|
|
34
|
+
validates :url, format: { with: /\Ahttps?:\/\/.+\z/ }
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Length
|
|
38
|
+
|
|
39
|
+
```ruby
|
|
40
|
+
validates :title, length: { maximum: 200 }
|
|
41
|
+
validates :password, length: { minimum: 8, maximum: 128 }
|
|
42
|
+
validates :code, length: { is: 6 }
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Inclusion
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
validates :status, inclusion: { in: %w[draft published archived] }
|
|
49
|
+
validates :role, inclusion: { in: ALLOWED_ROLES }
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Custom Validations
|
|
55
|
+
|
|
56
|
+
### Method Validation
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
59
|
+
class Webhook < ApplicationRecord
|
|
60
|
+
validate :validate_url
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
def validate_url
|
|
64
|
+
return if url.blank?
|
|
65
|
+
|
|
66
|
+
uri = URI.parse(url)
|
|
67
|
+
unless PERMITTED_SCHEMES.include?(uri.scheme)
|
|
68
|
+
errors.add(:url, "must use http or https")
|
|
69
|
+
end
|
|
70
|
+
rescue URI::InvalidURIError
|
|
71
|
+
errors.add(:url, "is not a valid URL")
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Validator Class
|
|
77
|
+
|
|
78
|
+
```ruby
|
|
79
|
+
class EmailValidator < ActiveModel::EachValidator
|
|
80
|
+
def validate_each(record, attribute, value)
|
|
81
|
+
unless value =~ URI::MailTo::EMAIL_REGEXP
|
|
82
|
+
record.errors.add(attribute, "is not a valid email")
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Usage
|
|
88
|
+
validates :email, email: true
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Conditional Validations
|
|
94
|
+
|
|
95
|
+
```ruby
|
|
96
|
+
validates :title, presence: true, if: :published?
|
|
97
|
+
validates :description, presence: true, unless: :draft?
|
|
98
|
+
validates :api_key, presence: true, on: :create
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Summary
|
|
104
|
+
|
|
105
|
+
- **Built-in**: presence, uniqueness, format, length, inclusion
|
|
106
|
+
- **Custom**: validate method or validator class
|
|
107
|
+
- **Conditional**: if, unless, on
|
|
108
|
+
- **Scope**: Uniqueness with scope option
|