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.
Files changed (196) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/agents/rails-concern.md +464 -0
  3. data/.claude/agents/rails-controller.md +424 -0
  4. data/.claude/agents/rails-hotwire.md +446 -0
  5. data/.claude/agents/rails-implement.md +374 -0
  6. data/.claude/agents/rails-job.md +334 -0
  7. data/.claude/agents/rails-lint.md +294 -0
  8. data/.claude/agents/rails-mailer.md +371 -0
  9. data/.claude/agents/rails-migration.md +449 -0
  10. data/.claude/agents/rails-model.md +420 -0
  11. data/.claude/agents/rails-policy.md +443 -0
  12. data/.claude/agents/rails-presenter.md +427 -0
  13. data/.claude/agents/rails-query.md +412 -0
  14. data/.claude/agents/rails-review.md +490 -0
  15. data/.claude/agents/rails-service.md +458 -0
  16. data/.claude/agents/rails-state-records.md +465 -0
  17. data/.claude/agents/rails-tdd.md +314 -0
  18. data/.claude/agents/rails-test.md +441 -0
  19. data/.claude/agents/rails-view-component.md +418 -0
  20. data/.claude/hooks/block-secrets.sh +52 -0
  21. data/.claude/settings.json +85 -0
  22. data/.claude/skills/action-cable-patterns/SKILL.md +296 -0
  23. data/.claude/skills/action-mailer-patterns/SKILL.md +295 -0
  24. data/.claude/skills/active-storage-setup/SKILL.md +311 -0
  25. data/.claude/skills/api-versioning/SKILL.md +294 -0
  26. data/.claude/skills/authentication-flow/SKILL.md +335 -0
  27. data/.claude/skills/authentication-flow/reference/current.md +248 -0
  28. data/.claude/skills/authentication-flow/reference/passwordless.md +253 -0
  29. data/.claude/skills/authentication-flow/reference/sessions.md +201 -0
  30. data/.claude/skills/authorization-pundit/SKILL.md +462 -0
  31. data/.claude/skills/caching-strategies/SKILL.md +350 -0
  32. data/.claude/skills/database-migrations/SKILL.md +354 -0
  33. data/.claude/skills/form-object-patterns/SKILL.md +399 -0
  34. data/.claude/skills/hotwire-patterns/SKILL.md +247 -0
  35. data/.claude/skills/hotwire-patterns/reference/stimulus.md +307 -0
  36. data/.claude/skills/hotwire-patterns/reference/tailwind-integration.md +112 -0
  37. data/.claude/skills/hotwire-patterns/reference/turbo-frames.md +158 -0
  38. data/.claude/skills/hotwire-patterns/reference/turbo-streams.md +218 -0
  39. data/.claude/skills/i18n-patterns/SKILL.md +320 -0
  40. data/.claude/skills/install/SKILL.md +367 -0
  41. data/.claude/skills/performance-optimization/SKILL.md +311 -0
  42. data/.claude/skills/rails-architecture/SKILL.md +259 -0
  43. data/.claude/skills/rails-architecture/reference/error-handling.md +333 -0
  44. data/.claude/skills/rails-architecture/reference/event-tracking.md +142 -0
  45. data/.claude/skills/rails-architecture/reference/layer-interactions.md +417 -0
  46. data/.claude/skills/rails-architecture/reference/multi-tenancy.md +152 -0
  47. data/.claude/skills/rails-architecture/reference/query-patterns.md +342 -0
  48. data/.claude/skills/rails-architecture/reference/service-patterns.md +286 -0
  49. data/.claude/skills/rails-architecture/reference/state-records.md +250 -0
  50. data/.claude/skills/rails-architecture/reference/testing-strategy.md +326 -0
  51. data/.claude/skills/rails-concern/SKILL.md +399 -0
  52. data/.claude/skills/rails-controller/SKILL.md +336 -0
  53. data/.claude/skills/rails-model-generator/SKILL.md +321 -0
  54. data/.claude/skills/rails-model-generator/reference/validations.md +298 -0
  55. data/.claude/skills/rails-presenter/SKILL.md +274 -0
  56. data/.claude/skills/rails-query-object/SKILL.md +289 -0
  57. data/.claude/skills/rails-service-object/SKILL.md +349 -0
  58. data/.claude/skills/solid-queue-setup/SKILL.md +307 -0
  59. data/.claude/skills/tdd-cycle/SKILL.md +359 -0
  60. data/.claude/skills/viewcomponent-patterns/SKILL.md +333 -0
  61. data/.gitignore +1 -0
  62. data/.rubocop.yml +2 -0
  63. data/.ruby-version +1 -1
  64. data/.vbw-planning/.notification-log.jsonl +192 -0
  65. data/.vbw-planning/.session-log.jsonl +871 -0
  66. data/.vbw-planning/PROJECT.md +51 -0
  67. data/.vbw-planning/REQUIREMENTS.md +50 -0
  68. data/.vbw-planning/SHIPPED.md +28 -0
  69. data/.vbw-planning/codebase/ARCHITECTURE.md +147 -0
  70. data/.vbw-planning/codebase/CONCERNS.md +99 -0
  71. data/.vbw-planning/codebase/CONVENTIONS.md +97 -0
  72. data/.vbw-planning/codebase/DEPENDENCIES.md +100 -0
  73. data/.vbw-planning/codebase/INDEX.md +86 -0
  74. data/.vbw-planning/codebase/META.md +42 -0
  75. data/.vbw-planning/codebase/PATTERNS.md +262 -0
  76. data/.vbw-planning/codebase/STACK.md +101 -0
  77. data/.vbw-planning/codebase/STRUCTURE.md +324 -0
  78. data/.vbw-planning/codebase/TESTING.md +154 -0
  79. data/.vbw-planning/config.json +12 -0
  80. data/.vbw-planning/discovery.json +24 -0
  81. data/.vbw-planning/milestones/default/ROADMAP.md +115 -0
  82. data/.vbw-planning/milestones/default/STATE.md +83 -0
  83. data/.vbw-planning/milestones/default/phases/01-coverage-analysis-quick-wins/PLAN-01-SUMMARY.md +56 -0
  84. data/.vbw-planning/milestones/default/phases/01-coverage-analysis-quick-wins/PLAN-01.md +187 -0
  85. data/.vbw-planning/milestones/default/phases/01-coverage-analysis-quick-wins/PLAN-02-SUMMARY.md +64 -0
  86. data/.vbw-planning/milestones/default/phases/01-coverage-analysis-quick-wins/PLAN-02.md +137 -0
  87. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-01-SUMMARY.md +67 -0
  88. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-01.md +142 -0
  89. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-02-SUMMARY.md +64 -0
  90. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-02.md +138 -0
  91. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-03-SUMMARY.md +85 -0
  92. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-03.md +147 -0
  93. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-04-SUMMARY.md +63 -0
  94. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-04.md +129 -0
  95. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-05-SUMMARY.md +74 -0
  96. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-05.md +154 -0
  97. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/03-VERIFICATION-wave1.md +303 -0
  98. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/03-VERIFICATION.md +510 -0
  99. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-01-SUMMARY.md +61 -0
  100. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-01.md +161 -0
  101. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-02-SUMMARY.md +66 -0
  102. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-02.md +132 -0
  103. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-03-SUMMARY.md +59 -0
  104. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-03.md +171 -0
  105. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-04-SUMMARY.md +56 -0
  106. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-04.md +152 -0
  107. data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/04-CONTEXT.md +33 -0
  108. data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-01-SUMMARY.md +42 -0
  109. data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-01.md +119 -0
  110. data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-02-SUMMARY.md +52 -0
  111. data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-02.md +195 -0
  112. data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-03-SUMMARY.md +79 -0
  113. data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-03.md +130 -0
  114. data/CHANGELOG.md +28 -0
  115. data/CLAUDE.md +179 -0
  116. data/Gemfile +8 -0
  117. data/Gemfile.lock +114 -101
  118. data/Rakefile +2 -0
  119. data/app/assets/builds/source_monitor/application.css +2076 -0
  120. data/app/assets/builds/source_monitor/application.js +2758 -0
  121. data/app/assets/builds/source_monitor/application.js.map +7 -0
  122. data/app/controllers/source_monitor/application_controller.rb +2 -0
  123. data/app/controllers/source_monitor/health_controller.rb +2 -0
  124. data/app/controllers/source_monitor/import_sessions/bulk_configuration.rb +106 -0
  125. data/app/controllers/source_monitor/import_sessions/entry_annotation.rb +187 -0
  126. data/app/controllers/source_monitor/import_sessions/health_check_management.rb +112 -0
  127. data/app/controllers/source_monitor/import_sessions/opml_parser.rb +130 -0
  128. data/app/controllers/source_monitor/import_sessions_controller.rb +6 -507
  129. data/app/controllers/source_monitor/items_controller.rb +2 -0
  130. data/app/controllers/source_monitor/sources_controller.rb +0 -14
  131. data/app/helpers/source_monitor/application_helper.rb +4 -112
  132. data/app/helpers/source_monitor/health_badge_helper.rb +69 -0
  133. data/app/helpers/source_monitor/table_sort_helper.rb +53 -0
  134. data/app/jobs/source_monitor/application_job.rb +2 -0
  135. data/app/models/source_monitor/application_record.rb +2 -0
  136. data/app/models/source_monitor/log_entry.rb +0 -2
  137. data/config/coverage_baseline.json +217 -1862
  138. data/config/routes.rb +2 -0
  139. data/db/migrate/20251009103000_add_feed_content_readability_to_sources.rb +2 -0
  140. data/db/migrate/20251014171659_add_performance_indexes.rb +2 -0
  141. data/db/migrate/20251014172525_add_fetch_status_check_constraint.rb +2 -0
  142. data/db/migrate/20251108120116_refresh_fetch_status_constraint.rb +2 -0
  143. data/db/migrate/20260210204022_add_composite_index_to_log_entries.rb +17 -0
  144. data/lib/source_monitor/assets/bundler.rb +2 -0
  145. data/lib/source_monitor/assets.rb +2 -0
  146. data/lib/source_monitor/configuration/authentication_settings.rb +62 -0
  147. data/lib/source_monitor/configuration/events.rb +60 -0
  148. data/lib/source_monitor/configuration/fetching_settings.rb +27 -0
  149. data/lib/source_monitor/configuration/health_settings.rb +27 -0
  150. data/lib/source_monitor/configuration/http_settings.rb +43 -0
  151. data/lib/source_monitor/configuration/model_definition.rb +108 -0
  152. data/lib/source_monitor/configuration/models.rb +36 -0
  153. data/lib/source_monitor/configuration/realtime_settings.rb +95 -0
  154. data/lib/source_monitor/configuration/retention_settings.rb +45 -0
  155. data/lib/source_monitor/configuration/scraper_registry.rb +67 -0
  156. data/lib/source_monitor/configuration/scraping_settings.rb +39 -0
  157. data/lib/source_monitor/configuration/validation_definition.rb +32 -0
  158. data/lib/source_monitor/configuration.rb +12 -579
  159. data/lib/source_monitor/dashboard/queries/recent_activity_query.rb +138 -0
  160. data/lib/source_monitor/dashboard/queries/stats_query.rb +71 -0
  161. data/lib/source_monitor/dashboard/queries.rb +2 -195
  162. data/lib/source_monitor/engine.rb +2 -0
  163. data/lib/source_monitor/fetching/feed_fetcher/adaptive_interval.rb +141 -0
  164. data/lib/source_monitor/fetching/feed_fetcher/entry_processor.rb +89 -0
  165. data/lib/source_monitor/fetching/feed_fetcher/source_updater.rb +200 -0
  166. data/lib/source_monitor/fetching/feed_fetcher.rb +37 -379
  167. data/lib/source_monitor/items/item_creator/content_extractor.rb +113 -0
  168. data/lib/source_monitor/items/item_creator/entry_parser/media_extraction.rb +96 -0
  169. data/lib/source_monitor/items/item_creator/entry_parser.rb +294 -0
  170. data/lib/source_monitor/items/item_creator.rb +28 -455
  171. data/lib/source_monitor/setup/bundle_installer.rb +2 -0
  172. data/lib/source_monitor/setup/cli.rb +2 -0
  173. data/lib/source_monitor/setup/dependency_checker.rb +2 -0
  174. data/lib/source_monitor/setup/detectors.rb +2 -0
  175. data/lib/source_monitor/setup/gemfile_editor.rb +2 -0
  176. data/lib/source_monitor/setup/initializer_patcher.rb +2 -0
  177. data/lib/source_monitor/setup/install_generator.rb +2 -0
  178. data/lib/source_monitor/setup/migration_installer.rb +2 -0
  179. data/lib/source_monitor/setup/node_installer.rb +2 -0
  180. data/lib/source_monitor/setup/prompter.rb +2 -0
  181. data/lib/source_monitor/setup/requirements.rb +2 -0
  182. data/lib/source_monitor/setup/shell_runner.rb +2 -0
  183. data/lib/source_monitor/setup/verification/action_cable_verifier.rb +2 -0
  184. data/lib/source_monitor/setup/verification/printer.rb +2 -0
  185. data/lib/source_monitor/setup/verification/result.rb +2 -0
  186. data/lib/source_monitor/setup/verification/runner.rb +2 -0
  187. data/lib/source_monitor/setup/verification/solid_queue_verifier.rb +2 -0
  188. data/lib/source_monitor/setup/verification/telemetry_logger.rb +2 -0
  189. data/lib/source_monitor/setup/workflow.rb +2 -0
  190. data/lib/source_monitor/version.rb +3 -1
  191. data/lib/source_monitor.rb +140 -58
  192. data/lib/tasks/source_monitor_assets.rake +2 -0
  193. data/lib/tasks/source_monitor_setup.rake +2 -0
  194. data/lib/tasks/source_monitor_tasks.rake +2 -0
  195. data/source_monitor.gemspec +3 -1
  196. metadata +144 -4
@@ -0,0 +1,441 @@
1
+ ---
2
+ name: rails-test
3
+ description: Generates comprehensive Minitest tests with fixtures for all layers. Use when writing tests, creating test files, adding test coverage, debugging test failures, or when the user mentions testing, minitest, fixtures, test helpers, or test organization.
4
+ tools: Read, Write, Edit, Bash, Glob, Grep
5
+ ---
6
+
7
+ # Comprehensive Minitest Testing
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
+ ## Test Directory Structure
21
+
22
+ ```
23
+ test/
24
+ ├── test_helper.rb
25
+ ├── application_system_test_case.rb
26
+ ├── components/ # ViewComponent tests
27
+ ├── controllers/ # Integration tests
28
+ ├── fixtures/ # Test data (YAML)
29
+ ├── jobs/ # ActiveJob tests
30
+ ├── mailers/ # ActionMailer tests
31
+ │ └── previews/ # Mailer previews
32
+ ├── models/ # Unit tests
33
+ │ └── concerns/ # Concern tests
34
+ ├── policies/ # Pundit policy tests
35
+ ├── presenters/ # Presenter tests
36
+ ├── queries/ # Query object tests
37
+ ├── services/ # Service object tests
38
+ └── system/ # Browser tests (Capybara)
39
+ ```
40
+
41
+ ## Test Helper Setup
42
+
43
+ ```ruby
44
+ # test/test_helper.rb
45
+ ENV["RAILS_ENV"] ||= "test"
46
+ require_relative "../config/environment"
47
+ require "rails/test_help"
48
+
49
+ module ActiveSupport
50
+ class TestCase
51
+ fixtures :all
52
+ parallelize(workers: :number_of_processors)
53
+
54
+ def sign_in_as(user)
55
+ post session_url, params: { email_address: user.email_address, password: "password" }
56
+ end
57
+
58
+ def sign_out
59
+ delete session_url
60
+ end
61
+
62
+ def assert_success(result)
63
+ assert result.success?, "Expected success but got failure: #{result.error}"
64
+ end
65
+
66
+ def assert_failure(result, code: nil)
67
+ assert result.failure?, "Expected failure but got success"
68
+ assert_equal code, result.code if code
69
+ end
70
+ end
71
+ end
72
+ ```
73
+
74
+ ```ruby
75
+ # test/application_system_test_case.rb
76
+ require "test_helper"
77
+
78
+ class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
79
+ driven_by :selenium, using: :headless_chrome, screen_size: [1400, 900]
80
+
81
+ def sign_in_as(user)
82
+ visit new_session_url
83
+ fill_in "Email", with: user.email_address
84
+ fill_in "Password", with: "password"
85
+ click_button "Sign in"
86
+ end
87
+ end
88
+ ```
89
+
90
+ ## Fixture Best Practices
91
+
92
+ Name fixtures after their state. Use ERB sparingly. Reference other fixtures by label.
93
+
94
+ ```yaml
95
+ # test/fixtures/users.yml
96
+ admin:
97
+ name: Admin User
98
+ email_address: admin@example.com
99
+ password_digest: <%= BCrypt::Password.create("password") %>
100
+ role: admin
101
+
102
+ regular:
103
+ name: Regular User
104
+ email_address: user@example.com
105
+ password_digest: <%= BCrypt::Password.create("password") %>
106
+ account: one
107
+
108
+ # test/fixtures/events.yml
109
+ upcoming:
110
+ name: Upcoming Conference
111
+ event_date: <%= 2.weeks.from_now.to_date %>
112
+ account: one
113
+
114
+ past:
115
+ name: Past Workshop
116
+ event_date: <%= 1.month.ago.to_date %>
117
+ account: one
118
+
119
+ other_account_event:
120
+ name: Other Event
121
+ event_date: <%= 1.week.from_now.to_date %>
122
+ account: two
123
+ ```
124
+
125
+ ## Model Tests
126
+
127
+ ```ruby
128
+ # test/models/event_test.rb
129
+ require "test_helper"
130
+
131
+ class EventTest < ActiveSupport::TestCase
132
+ test "requires name" do
133
+ event = Event.new(name: nil)
134
+ assert_not event.valid?
135
+ assert_includes event.errors[:name], "can't be blank"
136
+ end
137
+
138
+ test "requires event_date" do
139
+ event = Event.new(event_date: nil)
140
+ assert_not event.valid?
141
+ assert_includes event.errors[:event_date], "can't be blank"
142
+ end
143
+
144
+ test ".upcoming returns only future events" do
145
+ assert_includes Event.upcoming, events(:upcoming)
146
+ assert_not_includes Event.upcoming, events(:past)
147
+ end
148
+
149
+ test "#days_until returns days until event" do
150
+ event = Event.new(event_date: 5.days.from_now.to_date)
151
+ assert_equal 5, event.days_until
152
+ end
153
+ end
154
+ ```
155
+
156
+ ## Controller / Integration Tests
157
+
158
+ ```ruby
159
+ # test/controllers/events_controller_test.rb
160
+ require "test_helper"
161
+
162
+ class EventsControllerTest < ActionDispatch::IntegrationTest
163
+ setup do
164
+ @user = users(:regular)
165
+ @event = events(:upcoming)
166
+ sign_in_as @user
167
+ end
168
+
169
+ test "requires authentication" do
170
+ sign_out
171
+ get events_url
172
+ assert_redirected_to new_session_url
173
+ end
174
+
175
+ test "should get index" do
176
+ get events_url
177
+ assert_response :success
178
+ end
179
+
180
+ test "index only shows own account events" do
181
+ get events_url
182
+ assert_no_match events(:other_account_event).name, response.body
183
+ end
184
+
185
+ test "should create event" do
186
+ assert_difference("Event.count") do
187
+ post events_url, params: {
188
+ event: { name: "New Event", event_date: 1.week.from_now.to_date }
189
+ }
190
+ end
191
+ assert_redirected_to event_url(Event.last)
192
+ end
193
+
194
+ test "renders errors for invalid event" do
195
+ post events_url, params: { event: { name: "" } }
196
+ assert_response :unprocessable_entity
197
+ end
198
+
199
+ test "should update event" do
200
+ patch event_url(@event), params: { event: { name: "Updated" } }
201
+ assert_redirected_to event_url(@event)
202
+ assert_equal "Updated", @event.reload.name
203
+ end
204
+
205
+ test "should destroy event" do
206
+ assert_difference("Event.count", -1) do
207
+ delete event_url(@event)
208
+ end
209
+ assert_redirected_to events_url
210
+ end
211
+ end
212
+ ```
213
+
214
+ ## System Tests
215
+
216
+ ```ruby
217
+ # test/system/events_test.rb
218
+ require "application_system_test_case"
219
+
220
+ class EventsTest < ApplicationSystemTestCase
221
+ setup do
222
+ sign_in_as users(:regular)
223
+ end
224
+
225
+ test "creating an event" do
226
+ visit new_event_url
227
+ fill_in "Name", with: "Team Offsite"
228
+ fill_in "Event date", with: 1.month.from_now.to_date
229
+ click_button "Create Event"
230
+
231
+ assert_text "Event was successfully created"
232
+ assert_text "Team Offsite"
233
+ end
234
+
235
+ test "shows validation errors" do
236
+ visit new_event_url
237
+ click_button "Create Event"
238
+ assert_text "can't be blank"
239
+ end
240
+ end
241
+ ```
242
+
243
+ ## Service Tests
244
+
245
+ ```ruby
246
+ # test/services/orders/create_service_test.rb
247
+ require "test_helper"
248
+
249
+ class Orders::CreateServiceTest < ActiveSupport::TestCase
250
+ setup do
251
+ @user = users(:regular)
252
+ @product = products(:widget)
253
+ @service = Orders::CreateService.new
254
+ end
255
+
256
+ test "returns success with valid params" do
257
+ result = @service.call(user: @user, items: [{ product_id: @product.id, quantity: 2 }])
258
+ assert result.success?
259
+ assert_kind_of Order, result.data
260
+ end
261
+
262
+ test "creates order and line items" do
263
+ assert_difference ["Order.count", "LineItem.count"], 1 do
264
+ @service.call(user: @user, items: [{ product_id: @product.id, quantity: 1 }])
265
+ end
266
+ end
267
+
268
+ test "returns failure with empty items" do
269
+ result = @service.call(user: @user, items: [])
270
+ assert result.failure?
271
+ assert_equal :empty_cart, result.code
272
+ end
273
+
274
+ test "rolls back transaction on error" do
275
+ assert_no_difference "Order.count" do
276
+ @service.call(user: @user, items: [{ product_id: 0, quantity: 1 }])
277
+ end
278
+ end
279
+ end
280
+ ```
281
+
282
+ ## Query, Presenter, and Component Tests
283
+
284
+ ```ruby
285
+ # test/queries/active_events_query_test.rb
286
+ require "test_helper"
287
+
288
+ class ActiveEventsQueryTest < ActiveSupport::TestCase
289
+ setup do
290
+ @query = ActiveEventsQuery.new(account: accounts(:one))
291
+ end
292
+
293
+ test "returns active events for account" do
294
+ assert_includes @query.call, events(:upcoming)
295
+ end
296
+
297
+ test "excludes other account events" do
298
+ assert_not_includes @query.call, events(:other_account_event)
299
+ end
300
+ end
301
+
302
+ # test/presenters/event_presenter_test.rb
303
+ require "test_helper"
304
+
305
+ class EventPresenterTest < ActiveSupport::TestCase
306
+ test "#status_badge returns HTML-safe string" do
307
+ presenter = EventPresenter.new(events(:upcoming))
308
+ assert_predicate presenter.status_badge, :html_safe?
309
+ end
310
+
311
+ test "#formatted_date with nil date returns TBD" do
312
+ presenter = EventPresenter.new(Event.new(event_date: nil))
313
+ assert_match "TBD", presenter.formatted_date
314
+ end
315
+ end
316
+
317
+ # test/components/event_card_component_test.rb
318
+ require "test_helper"
319
+
320
+ class EventCardComponentTest < ViewComponent::TestCase
321
+ test "renders event name" do
322
+ event = events(:upcoming)
323
+ render_inline(EventCardComponent.new(event: event))
324
+ assert_text event.name
325
+ end
326
+
327
+ test "renders status badge" do
328
+ render_inline(EventCardComponent.new(event: events(:upcoming)))
329
+ assert_selector ".badge"
330
+ end
331
+ end
332
+ ```
333
+
334
+ ## Policy Tests
335
+
336
+ ```ruby
337
+ # test/policies/event_policy_test.rb
338
+ require "test_helper"
339
+
340
+ class EventPolicyTest < ActiveSupport::TestCase
341
+ setup do
342
+ @owner = users(:regular)
343
+ @other = users(:other_account)
344
+ @event = events(:upcoming)
345
+ end
346
+
347
+ test "owner can show" do
348
+ assert EventPolicy.new(@owner, @event).show?
349
+ end
350
+
351
+ test "non-owner cannot show" do
352
+ assert_not EventPolicy.new(@other, @event).show?
353
+ end
354
+
355
+ test "scope returns only own account events" do
356
+ scope = EventPolicy::Scope.new(@owner, Event).resolve
357
+ assert_includes scope, @event
358
+ assert_not_includes scope, events(:other_account_event)
359
+ end
360
+ end
361
+ ```
362
+
363
+ ## Job and Mailer Tests
364
+
365
+ ```ruby
366
+ # test/jobs/fulfill_order_job_test.rb
367
+ require "test_helper"
368
+
369
+ class FulfillOrderJobTest < ActiveJob::TestCase
370
+ test "fulfills the order" do
371
+ order = orders(:pending)
372
+ FulfillOrderJob.perform_now(order.id)
373
+ assert_not_nil order.reload.fulfilled_at
374
+ end
375
+ end
376
+
377
+ # test/mailers/user_mailer_test.rb
378
+ require "test_helper"
379
+
380
+ class UserMailerTest < ActionMailer::TestCase
381
+ test "welcome email" do
382
+ user = users(:regular)
383
+ email = UserMailer.welcome(user)
384
+
385
+ assert_emails 1 do
386
+ email.deliver_now
387
+ end
388
+
389
+ assert_equal [user.email_address], email.to
390
+ assert_match "Welcome", email.subject
391
+ assert_match user.name, email.body.encoded
392
+ end
393
+ end
394
+ ```
395
+
396
+ ## Performance Tips
397
+
398
+ | Test Type | Speed | Write First? |
399
+ |-----------|-------|--------------|
400
+ | Model / Service / Query / Policy | Fast | Yes |
401
+ | Component / Controller | Medium | For key features |
402
+ | System | Slow | Critical paths only |
403
+
404
+ - Prefer model/service tests over system tests (10-100x faster)
405
+ - Use `parallelize(workers: :number_of_processors)` for multi-core
406
+ - Use `assert_no_difference` instead of checking count before/after
407
+ - Avoid `sleep` in tests; use Capybara's built-in waiting
408
+
409
+ ## Running Tests
410
+
411
+ ```bash
412
+ bin/rails test # All tests
413
+ bin/rails test test/models/event_test.rb # Single file
414
+ bin/rails test test/models/event_test.rb:15 # Single test by line
415
+ bin/rails test -n "test_requires_name" # By name
416
+ bin/rails test --verbose # Verbose output
417
+ bin/rails test:system # System tests only
418
+ ```
419
+
420
+ ## Test Generation Checklist
421
+
422
+ - [ ] Model tests: validations, scopes, associations, methods
423
+ - [ ] Service tests: success path, failure path, edge cases
424
+ - [ ] Controller tests: auth, CRUD actions, error responses
425
+ - [ ] Policy tests: all actions, scope filtering
426
+ - [ ] Fixtures with meaningful names and minimal data
427
+ - [ ] System tests for 1-2 critical user paths
428
+ - [ ] Component tests if ViewComponents used
429
+ - [ ] Job tests for background processing
430
+ - [ ] Mailer tests for email content
431
+
432
+ ## Anti-Patterns
433
+
434
+ | Anti-Pattern | Problem | Solution |
435
+ |--------------|---------|----------|
436
+ | Using FactoryBot / RSpec | Not our convention | Use Minitest + fixtures |
437
+ | Testing implementation | Brittle, breaks on refactor | Test behavior and outcomes |
438
+ | Mystery guest | Unclear fixture references | Use descriptive fixture names |
439
+ | Too many system tests | Slow test suite | Prefer unit tests |
440
+ | No assertions | Test passes but verifies nothing | Every test needs an assertion |
441
+ | Hardcoded IDs | Breaks when fixtures change | Reference fixtures by name |