source_monitor 0.2.0 → 0.3.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/agents/rails-concern.md +464 -0
- data/.claude/agents/rails-controller.md +424 -0
- data/.claude/agents/rails-hotwire.md +446 -0
- data/.claude/agents/rails-implement.md +374 -0
- data/.claude/agents/rails-job.md +334 -0
- data/.claude/agents/rails-lint.md +294 -0
- data/.claude/agents/rails-mailer.md +371 -0
- data/.claude/agents/rails-migration.md +449 -0
- data/.claude/agents/rails-model.md +420 -0
- data/.claude/agents/rails-policy.md +443 -0
- data/.claude/agents/rails-presenter.md +427 -0
- data/.claude/agents/rails-query.md +412 -0
- data/.claude/agents/rails-review.md +490 -0
- data/.claude/agents/rails-service.md +458 -0
- data/.claude/agents/rails-state-records.md +465 -0
- data/.claude/agents/rails-tdd.md +314 -0
- data/.claude/agents/rails-test.md +441 -0
- data/.claude/agents/rails-view-component.md +418 -0
- data/.claude/hooks/block-secrets.sh +52 -0
- data/.claude/settings.json +85 -0
- data/.claude/skills/action-cable-patterns/SKILL.md +296 -0
- data/.claude/skills/action-mailer-patterns/SKILL.md +295 -0
- data/.claude/skills/active-storage-setup/SKILL.md +311 -0
- data/.claude/skills/api-versioning/SKILL.md +294 -0
- data/.claude/skills/authentication-flow/SKILL.md +335 -0
- data/.claude/skills/authentication-flow/reference/current.md +248 -0
- data/.claude/skills/authentication-flow/reference/passwordless.md +253 -0
- data/.claude/skills/authentication-flow/reference/sessions.md +201 -0
- data/.claude/skills/authorization-pundit/SKILL.md +462 -0
- data/.claude/skills/caching-strategies/SKILL.md +350 -0
- data/.claude/skills/database-migrations/SKILL.md +354 -0
- data/.claude/skills/form-object-patterns/SKILL.md +399 -0
- data/.claude/skills/hotwire-patterns/SKILL.md +247 -0
- data/.claude/skills/hotwire-patterns/reference/stimulus.md +307 -0
- data/.claude/skills/hotwire-patterns/reference/tailwind-integration.md +112 -0
- data/.claude/skills/hotwire-patterns/reference/turbo-frames.md +158 -0
- data/.claude/skills/hotwire-patterns/reference/turbo-streams.md +218 -0
- data/.claude/skills/i18n-patterns/SKILL.md +320 -0
- data/.claude/skills/install/SKILL.md +367 -0
- data/.claude/skills/performance-optimization/SKILL.md +311 -0
- data/.claude/skills/rails-architecture/SKILL.md +259 -0
- data/.claude/skills/rails-architecture/reference/error-handling.md +333 -0
- data/.claude/skills/rails-architecture/reference/event-tracking.md +142 -0
- data/.claude/skills/rails-architecture/reference/layer-interactions.md +417 -0
- data/.claude/skills/rails-architecture/reference/multi-tenancy.md +152 -0
- data/.claude/skills/rails-architecture/reference/query-patterns.md +342 -0
- data/.claude/skills/rails-architecture/reference/service-patterns.md +286 -0
- data/.claude/skills/rails-architecture/reference/state-records.md +250 -0
- data/.claude/skills/rails-architecture/reference/testing-strategy.md +326 -0
- data/.claude/skills/rails-concern/SKILL.md +399 -0
- data/.claude/skills/rails-controller/SKILL.md +336 -0
- data/.claude/skills/rails-model-generator/SKILL.md +321 -0
- data/.claude/skills/rails-model-generator/reference/validations.md +298 -0
- data/.claude/skills/rails-presenter/SKILL.md +274 -0
- data/.claude/skills/rails-query-object/SKILL.md +289 -0
- data/.claude/skills/rails-service-object/SKILL.md +349 -0
- data/.claude/skills/solid-queue-setup/SKILL.md +307 -0
- data/.claude/skills/tdd-cycle/SKILL.md +359 -0
- data/.claude/skills/viewcomponent-patterns/SKILL.md +333 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +2 -0
- data/.ruby-version +1 -1
- data/.vbw-planning/.notification-log.jsonl +192 -0
- data/.vbw-planning/.session-log.jsonl +871 -0
- data/.vbw-planning/PROJECT.md +51 -0
- data/.vbw-planning/REQUIREMENTS.md +50 -0
- data/.vbw-planning/SHIPPED.md +28 -0
- data/.vbw-planning/codebase/ARCHITECTURE.md +147 -0
- data/.vbw-planning/codebase/CONCERNS.md +99 -0
- data/.vbw-planning/codebase/CONVENTIONS.md +97 -0
- data/.vbw-planning/codebase/DEPENDENCIES.md +100 -0
- data/.vbw-planning/codebase/INDEX.md +86 -0
- data/.vbw-planning/codebase/META.md +42 -0
- data/.vbw-planning/codebase/PATTERNS.md +262 -0
- data/.vbw-planning/codebase/STACK.md +101 -0
- data/.vbw-planning/codebase/STRUCTURE.md +324 -0
- data/.vbw-planning/codebase/TESTING.md +154 -0
- data/.vbw-planning/config.json +12 -0
- data/.vbw-planning/discovery.json +24 -0
- data/.vbw-planning/milestones/default/ROADMAP.md +115 -0
- data/.vbw-planning/milestones/default/STATE.md +83 -0
- data/.vbw-planning/milestones/default/phases/01-coverage-analysis-quick-wins/PLAN-01-SUMMARY.md +56 -0
- data/.vbw-planning/milestones/default/phases/01-coverage-analysis-quick-wins/PLAN-01.md +187 -0
- data/.vbw-planning/milestones/default/phases/01-coverage-analysis-quick-wins/PLAN-02-SUMMARY.md +64 -0
- data/.vbw-planning/milestones/default/phases/01-coverage-analysis-quick-wins/PLAN-02.md +137 -0
- data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-01-SUMMARY.md +67 -0
- data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-01.md +142 -0
- data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-02-SUMMARY.md +64 -0
- data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-02.md +138 -0
- data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-03-SUMMARY.md +85 -0
- data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-03.md +147 -0
- data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-04-SUMMARY.md +63 -0
- data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-04.md +129 -0
- data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-05-SUMMARY.md +74 -0
- data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-05.md +154 -0
- data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/03-VERIFICATION-wave1.md +303 -0
- data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/03-VERIFICATION.md +510 -0
- data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-01-SUMMARY.md +61 -0
- data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-01.md +161 -0
- data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-02-SUMMARY.md +66 -0
- data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-02.md +132 -0
- data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-03-SUMMARY.md +59 -0
- data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-03.md +171 -0
- data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-04-SUMMARY.md +56 -0
- data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-04.md +152 -0
- data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/04-CONTEXT.md +33 -0
- data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-01-SUMMARY.md +42 -0
- data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-01.md +119 -0
- data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-02-SUMMARY.md +52 -0
- data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-02.md +195 -0
- data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-03-SUMMARY.md +79 -0
- data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-03.md +130 -0
- data/CHANGELOG.md +28 -0
- data/CLAUDE.md +179 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +114 -101
- data/Rakefile +2 -0
- data/app/assets/builds/source_monitor/application.css +2076 -0
- data/app/assets/builds/source_monitor/application.js +2758 -0
- data/app/assets/builds/source_monitor/application.js.map +7 -0
- data/app/controllers/source_monitor/application_controller.rb +2 -0
- data/app/controllers/source_monitor/health_controller.rb +2 -0
- data/app/controllers/source_monitor/import_sessions/bulk_configuration.rb +106 -0
- data/app/controllers/source_monitor/import_sessions/entry_annotation.rb +187 -0
- data/app/controllers/source_monitor/import_sessions/health_check_management.rb +112 -0
- data/app/controllers/source_monitor/import_sessions/opml_parser.rb +130 -0
- data/app/controllers/source_monitor/import_sessions_controller.rb +6 -507
- data/app/controllers/source_monitor/items_controller.rb +2 -0
- data/app/controllers/source_monitor/sources_controller.rb +0 -14
- data/app/helpers/source_monitor/application_helper.rb +4 -112
- data/app/helpers/source_monitor/health_badge_helper.rb +69 -0
- data/app/helpers/source_monitor/table_sort_helper.rb +53 -0
- data/app/jobs/source_monitor/application_job.rb +2 -0
- data/app/models/source_monitor/application_record.rb +2 -0
- data/app/models/source_monitor/log_entry.rb +0 -2
- data/config/coverage_baseline.json +217 -1862
- data/config/routes.rb +2 -0
- data/db/migrate/20251009103000_add_feed_content_readability_to_sources.rb +2 -0
- data/db/migrate/20251014171659_add_performance_indexes.rb +2 -0
- data/db/migrate/20251014172525_add_fetch_status_check_constraint.rb +2 -0
- data/db/migrate/20251108120116_refresh_fetch_status_constraint.rb +2 -0
- data/db/migrate/20260210204022_add_composite_index_to_log_entries.rb +17 -0
- data/lib/source_monitor/assets/bundler.rb +2 -0
- data/lib/source_monitor/assets.rb +2 -0
- data/lib/source_monitor/configuration/authentication_settings.rb +62 -0
- data/lib/source_monitor/configuration/events.rb +60 -0
- data/lib/source_monitor/configuration/fetching_settings.rb +27 -0
- data/lib/source_monitor/configuration/health_settings.rb +27 -0
- data/lib/source_monitor/configuration/http_settings.rb +43 -0
- data/lib/source_monitor/configuration/model_definition.rb +108 -0
- data/lib/source_monitor/configuration/models.rb +36 -0
- data/lib/source_monitor/configuration/realtime_settings.rb +95 -0
- data/lib/source_monitor/configuration/retention_settings.rb +45 -0
- data/lib/source_monitor/configuration/scraper_registry.rb +67 -0
- data/lib/source_monitor/configuration/scraping_settings.rb +39 -0
- data/lib/source_monitor/configuration/validation_definition.rb +32 -0
- data/lib/source_monitor/configuration.rb +12 -579
- data/lib/source_monitor/dashboard/queries/recent_activity_query.rb +138 -0
- data/lib/source_monitor/dashboard/queries/stats_query.rb +71 -0
- data/lib/source_monitor/dashboard/queries.rb +2 -195
- data/lib/source_monitor/engine.rb +2 -0
- data/lib/source_monitor/fetching/feed_fetcher/adaptive_interval.rb +141 -0
- data/lib/source_monitor/fetching/feed_fetcher/entry_processor.rb +89 -0
- data/lib/source_monitor/fetching/feed_fetcher/source_updater.rb +200 -0
- data/lib/source_monitor/fetching/feed_fetcher.rb +37 -379
- data/lib/source_monitor/items/item_creator/content_extractor.rb +113 -0
- data/lib/source_monitor/items/item_creator/entry_parser/media_extraction.rb +96 -0
- data/lib/source_monitor/items/item_creator/entry_parser.rb +294 -0
- data/lib/source_monitor/items/item_creator.rb +28 -455
- data/lib/source_monitor/setup/bundle_installer.rb +2 -0
- data/lib/source_monitor/setup/cli.rb +2 -0
- data/lib/source_monitor/setup/dependency_checker.rb +2 -0
- data/lib/source_monitor/setup/detectors.rb +2 -0
- data/lib/source_monitor/setup/gemfile_editor.rb +2 -0
- data/lib/source_monitor/setup/initializer_patcher.rb +2 -0
- data/lib/source_monitor/setup/install_generator.rb +2 -0
- data/lib/source_monitor/setup/migration_installer.rb +2 -0
- data/lib/source_monitor/setup/node_installer.rb +2 -0
- data/lib/source_monitor/setup/prompter.rb +2 -0
- data/lib/source_monitor/setup/requirements.rb +2 -0
- data/lib/source_monitor/setup/shell_runner.rb +2 -0
- data/lib/source_monitor/setup/verification/action_cable_verifier.rb +2 -0
- data/lib/source_monitor/setup/verification/printer.rb +2 -0
- data/lib/source_monitor/setup/verification/result.rb +2 -0
- data/lib/source_monitor/setup/verification/runner.rb +2 -0
- data/lib/source_monitor/setup/verification/solid_queue_verifier.rb +2 -0
- data/lib/source_monitor/setup/verification/telemetry_logger.rb +2 -0
- data/lib/source_monitor/setup/workflow.rb +2 -0
- data/lib/source_monitor/version.rb +3 -1
- data/lib/source_monitor.rb +140 -58
- data/lib/tasks/source_monitor_assets.rake +2 -0
- data/lib/tasks/source_monitor_setup.rake +2 -0
- data/lib/tasks/source_monitor_tasks.rake +2 -0
- data/source_monitor.gemspec +3 -1
- metadata +144 -4
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rails-tdd
|
|
3
|
+
description: Guides Red-Green-Refactor TDD workflow using Minitest and fixtures. Use when the user wants to implement a feature using TDD, write tests first, follow test-driven practices, or mentions red-green-refactor, test-driven development, or writing tests before code.
|
|
4
|
+
tools: Read, Write, Edit, Bash, Glob, Grep
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# TDD Workflow — Red-Green-Refactor
|
|
8
|
+
|
|
9
|
+
## Project Conventions
|
|
10
|
+
- **Testing:** Minitest + fixtures (NEVER RSpec or FactoryBot)
|
|
11
|
+
- **Components:** ViewComponents for reusable UI (partials OK for simple one-offs)
|
|
12
|
+
- **Authorization:** Pundit policies (deny by default)
|
|
13
|
+
- **Jobs:** Solid Queue, shallow jobs, `_later`/`_now` naming
|
|
14
|
+
- **Frontend:** Hotwire (Turbo + Stimulus) + Tailwind CSS
|
|
15
|
+
- **State:** State-as-records for business state (booleans only for technical flags)
|
|
16
|
+
- **Architecture:** Rich models first, service objects for multi-model orchestration
|
|
17
|
+
- **Routing:** Everything-is-CRUD (new resource over new action)
|
|
18
|
+
- **Quality:** RuboCop (omakase) + Brakeman
|
|
19
|
+
|
|
20
|
+
## The TDD Cycle
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
1. RED → Write a failing test that describes desired behavior
|
|
24
|
+
2. GREEN → Write the minimum code to make the test pass
|
|
25
|
+
3. REFACTOR → Improve code while keeping tests green
|
|
26
|
+
4. REPEAT → Next behavior
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Workflow Checklist
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
- [ ] Understand the requirement (input, output, edge cases)
|
|
33
|
+
- [ ] Choose test type (model/controller/system/service/etc.)
|
|
34
|
+
- [ ] Create or update fixtures as needed
|
|
35
|
+
- [ ] Write ONE failing test (RED)
|
|
36
|
+
- [ ] Run test — verify it FAILS with expected error
|
|
37
|
+
- [ ] Write minimum code to pass (GREEN)
|
|
38
|
+
- [ ] Run test — verify it PASSES
|
|
39
|
+
- [ ] Refactor if needed (improve code, keep tests green)
|
|
40
|
+
- [ ] Run ALL tests — verify nothing broke
|
|
41
|
+
- [ ] Repeat for the next behavior
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Choosing the Right Test Type
|
|
45
|
+
|
|
46
|
+
| What You're Building | Test Type | Base Class |
|
|
47
|
+
|---------------------|-----------|------------|
|
|
48
|
+
| Validation, scope, method | Model test | `ActiveSupport::TestCase` |
|
|
49
|
+
| HTTP endpoint | Controller test | `ActionDispatch::IntegrationTest` |
|
|
50
|
+
| Multi-model business logic | Service test | `ActiveSupport::TestCase` |
|
|
51
|
+
| Complex query | Query test | `ActiveSupport::TestCase` |
|
|
52
|
+
| Authorization rule | Policy test | `ActiveSupport::TestCase` |
|
|
53
|
+
| Reusable UI component | Component test | `ViewComponent::TestCase` |
|
|
54
|
+
| Background processing | Job test | `ActiveJob::TestCase` |
|
|
55
|
+
| Email content | Mailer test | `ActionMailer::TestCase` |
|
|
56
|
+
| Full user flow with JS | System test | `ApplicationSystemTestCase` |
|
|
57
|
+
|
|
58
|
+
## Writing Good Failing Tests (RED)
|
|
59
|
+
|
|
60
|
+
### One Concept Per Test
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
# GOOD: One concept each
|
|
64
|
+
test "requires title" do
|
|
65
|
+
post = Post.new(title: nil)
|
|
66
|
+
assert_not post.valid?
|
|
67
|
+
assert_includes post.errors[:title], "can't be blank"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
test "requires body" do
|
|
71
|
+
post = Post.new(body: nil)
|
|
72
|
+
assert_not post.valid?
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# BAD: Multiple concepts
|
|
76
|
+
test "validates presence" do
|
|
77
|
+
post = Post.new(title: nil, body: nil)
|
|
78
|
+
assert_not post.valid?
|
|
79
|
+
assert_includes post.errors[:title], "can't be blank"
|
|
80
|
+
assert_includes post.errors[:body], "can't be blank"
|
|
81
|
+
end
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Descriptive Test Names
|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
# GOOD: Names describe behavior
|
|
88
|
+
test "published scope returns only published posts"
|
|
89
|
+
test "returns failure when cart is empty"
|
|
90
|
+
test "admin can destroy any event"
|
|
91
|
+
|
|
92
|
+
# BAD: Vague
|
|
93
|
+
test "scope works"
|
|
94
|
+
test "handles error"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Verify the Failure
|
|
98
|
+
|
|
99
|
+
Run the test. The failure should tell you what's missing:
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
# GOOD failures: NameError: uninitialized constant Post
|
|
103
|
+
# NoMethodError: undefined method 'publish!'
|
|
104
|
+
# BAD failures: ActiveRecord::ConnectionNotEstablished
|
|
105
|
+
# SyntaxError: unexpected end
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Minimal Implementation (GREEN)
|
|
109
|
+
|
|
110
|
+
Write the MINIMUM code to pass — no optimization, no untested edge cases:
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
# Test says: requires title
|
|
114
|
+
# GREEN: Just add the validation
|
|
115
|
+
validates :title, presence: true
|
|
116
|
+
|
|
117
|
+
# NOT this (over-engineering without tests):
|
|
118
|
+
validates :title, presence: true, length: { minimum: 3, maximum: 255 },
|
|
119
|
+
uniqueness: { scope: :account_id }
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Refactoring Rules
|
|
123
|
+
|
|
124
|
+
1. Only refactor when tests are GREEN
|
|
125
|
+
2. One change at a time
|
|
126
|
+
3. Run tests after EACH change
|
|
127
|
+
4. If tests break, undo the last change
|
|
128
|
+
5. Don't add new features during refactoring
|
|
129
|
+
|
|
130
|
+
## Example: TDD a Complete Model
|
|
131
|
+
|
|
132
|
+
**Requirement:** Event with name, date, upcoming scope, days_until method
|
|
133
|
+
|
|
134
|
+
```ruby
|
|
135
|
+
# Cycle 1: RED — test validation
|
|
136
|
+
test "requires name" do
|
|
137
|
+
event = Event.new(name: nil)
|
|
138
|
+
assert_not event.valid?
|
|
139
|
+
assert_includes event.errors[:name], "can't be blank"
|
|
140
|
+
end
|
|
141
|
+
# GREEN: validates :name, presence: true
|
|
142
|
+
|
|
143
|
+
# Cycle 2: RED — test another validation
|
|
144
|
+
test "requires event_date" do
|
|
145
|
+
event = Event.new(event_date: nil)
|
|
146
|
+
assert_not event.valid?
|
|
147
|
+
end
|
|
148
|
+
# GREEN: validates :event_date, presence: true
|
|
149
|
+
|
|
150
|
+
# Cycle 3: RED — test scope
|
|
151
|
+
test ".upcoming returns only future events" do
|
|
152
|
+
assert_includes Event.upcoming, events(:upcoming)
|
|
153
|
+
assert_not_includes Event.upcoming, events(:past)
|
|
154
|
+
end
|
|
155
|
+
# GREEN: scope :upcoming, -> { where("event_date >= ?", Date.current) }
|
|
156
|
+
|
|
157
|
+
# Cycle 4: RED — test method
|
|
158
|
+
test "#days_until returns days until event" do
|
|
159
|
+
event = Event.new(event_date: 5.days.from_now.to_date)
|
|
160
|
+
assert_equal 5, event.days_until
|
|
161
|
+
end
|
|
162
|
+
# GREEN: def days_until = (event_date - Date.current).to_i
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Example: TDD a Controller Action
|
|
166
|
+
|
|
167
|
+
```ruby
|
|
168
|
+
# Cycle 1: Authentication required
|
|
169
|
+
test "create requires authentication" do
|
|
170
|
+
post events_url, params: { event: { name: "Test" } }
|
|
171
|
+
assert_redirected_to new_session_url
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Cycle 2: Successful create
|
|
175
|
+
test "creates event with valid params" do
|
|
176
|
+
sign_in_as users(:regular)
|
|
177
|
+
assert_difference("Event.count") do
|
|
178
|
+
post events_url, params: {
|
|
179
|
+
event: { name: "New Event", event_date: 1.week.from_now.to_date }
|
|
180
|
+
}
|
|
181
|
+
end
|
|
182
|
+
assert_redirected_to event_url(Event.last)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Cycle 3: Validation errors
|
|
186
|
+
test "renders errors for invalid params" do
|
|
187
|
+
sign_in_as users(:regular)
|
|
188
|
+
assert_no_difference("Event.count") do
|
|
189
|
+
post events_url, params: { event: { name: "" } }
|
|
190
|
+
end
|
|
191
|
+
assert_response :unprocessable_entity
|
|
192
|
+
end
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Example: TDD a Service Object
|
|
196
|
+
|
|
197
|
+
```ruby
|
|
198
|
+
# Cycle 1: Success path
|
|
199
|
+
test "returns success with valid params" do
|
|
200
|
+
result = @service.call(user: @user, items: [{ product_id: @product.id, quantity: 1 }])
|
|
201
|
+
assert result.success?
|
|
202
|
+
assert_kind_of Order, result.data
|
|
203
|
+
end
|
|
204
|
+
# GREEN: Minimal service that creates order and returns Result.success
|
|
205
|
+
|
|
206
|
+
# Cycle 2: Line items
|
|
207
|
+
test "creates line items" do
|
|
208
|
+
assert_difference "LineItem.count", 1 do
|
|
209
|
+
@service.call(user: @user, items: [{ product_id: @product.id, quantity: 2 }])
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
# GREEN: Add line item creation to service
|
|
213
|
+
|
|
214
|
+
# Cycle 3: Failure path
|
|
215
|
+
test "returns failure with empty items" do
|
|
216
|
+
result = @service.call(user: @user, items: [])
|
|
217
|
+
assert result.failure?
|
|
218
|
+
assert_equal :empty_cart, result.code
|
|
219
|
+
end
|
|
220
|
+
# GREEN: Add guard clause
|
|
221
|
+
|
|
222
|
+
# Cycle 4: Transaction safety
|
|
223
|
+
test "rolls back on error" do
|
|
224
|
+
assert_no_difference "Order.count" do
|
|
225
|
+
@service.call(user: @user, items: [{ product_id: 0, quantity: 1 }])
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
# GREEN: Wrap in transaction
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## TDD Order for a New Feature
|
|
232
|
+
|
|
233
|
+
Build from fastest to slowest, most isolated to most integrated:
|
|
234
|
+
|
|
235
|
+
1. **Model tests** — Validations, scopes, methods
|
|
236
|
+
2. **Policy tests** — Authorization rules
|
|
237
|
+
3. **Service tests** — Business logic (if needed)
|
|
238
|
+
4. **Controller tests** — HTTP integration
|
|
239
|
+
5. **Component tests** — UI components (if applicable)
|
|
240
|
+
6. **System tests** — 1-2 critical end-to-end flows
|
|
241
|
+
|
|
242
|
+
## Anti-Patterns to Avoid
|
|
243
|
+
|
|
244
|
+
### Testing Implementation, Not Behavior
|
|
245
|
+
|
|
246
|
+
```ruby
|
|
247
|
+
# BAD: Tests how it works
|
|
248
|
+
test "calls update! on the record" do
|
|
249
|
+
assert_called(order, :update!) { order.fulfill! }
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# GOOD: Tests what it does
|
|
253
|
+
test "sets fulfilled_at timestamp" do
|
|
254
|
+
order.fulfill!
|
|
255
|
+
assert_not_nil order.reload.fulfilled_at
|
|
256
|
+
end
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Mystery Guest
|
|
260
|
+
|
|
261
|
+
```ruby
|
|
262
|
+
# BAD: users(:one) — what state?
|
|
263
|
+
sign_in_as users(:one)
|
|
264
|
+
|
|
265
|
+
# GOOD: Descriptive fixture name
|
|
266
|
+
sign_in_as users(:regular)
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Over-Specifying
|
|
270
|
+
|
|
271
|
+
```ruby
|
|
272
|
+
# BAD: Brittle — breaks if wording changes
|
|
273
|
+
assert_equal "Your cart is empty. Please add items.", result.error
|
|
274
|
+
|
|
275
|
+
# GOOD: Tests concept
|
|
276
|
+
assert result.failure?
|
|
277
|
+
assert_equal :empty_cart, result.code
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Skipping the RED Step
|
|
281
|
+
|
|
282
|
+
Always see the test fail first. If it passes immediately, either the behavior already exists or the test isn't testing what you think.
|
|
283
|
+
|
|
284
|
+
### Too Many System Tests
|
|
285
|
+
|
|
286
|
+
```ruby
|
|
287
|
+
# BAD: System test for something a model test covers
|
|
288
|
+
test "title is required" do
|
|
289
|
+
visit new_post_url
|
|
290
|
+
click_button "Create Post"
|
|
291
|
+
assert_text "can't be blank"
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# GOOD: Model test for validations, system test only for critical flow
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## Running Tests During TDD
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
bin/rails test test/models/event_test.rb # Single file
|
|
301
|
+
bin/rails test test/models/event_test.rb:15 # Single test by line
|
|
302
|
+
bin/rails test test/models/ # All model tests
|
|
303
|
+
bin/rails test # Full suite before commit
|
|
304
|
+
bin/rails test --verbose # See test names
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
## TDD Session Checklist
|
|
308
|
+
|
|
309
|
+
- [ ] Understand the full requirement
|
|
310
|
+
- [ ] Identify all behaviors to test
|
|
311
|
+
- [ ] Create/update fixtures for test scenarios
|
|
312
|
+
- [ ] Plan test order (model -> policy -> service -> controller -> system)
|
|
313
|
+
- [ ] Start the RED-GREEN-REFACTOR cycle
|
|
314
|
+
- [ ] Run full test suite before declaring done
|