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,343 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rails-testing-controllers
|
|
3
|
+
description: "Rails testing: controller tests, system tests, and integration tests"
|
|
4
|
+
group: rails
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Testing Controllers & Integration
|
|
8
|
+
|
|
9
|
+
## Controller Tests
|
|
10
|
+
|
|
11
|
+
### Basic CRUD Tests
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
class BoardsControllerTest < ActionDispatch::IntegrationTest
|
|
15
|
+
setup do
|
|
16
|
+
sign_in_as :kevin
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
test "index shows boards" do
|
|
20
|
+
get boards_path
|
|
21
|
+
|
|
22
|
+
assert_response :success
|
|
23
|
+
assert_select "h1", "Boards"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
test "show displays board" do
|
|
27
|
+
board = boards(:writebook)
|
|
28
|
+
|
|
29
|
+
get board_path(board)
|
|
30
|
+
|
|
31
|
+
assert_response :success
|
|
32
|
+
assert_select "h1", board.name
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
test "create creates board" do
|
|
36
|
+
assert_difference -> { Board.count }, +1 do
|
|
37
|
+
post boards_path, params: { board: { name: "New Board" } }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
assert_redirected_to board_path(Board.last)
|
|
41
|
+
assert_equal "New Board", Board.last.name
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
test "update updates board" do
|
|
45
|
+
board = boards(:writebook)
|
|
46
|
+
|
|
47
|
+
patch board_path(board), params: { board: { name: "Updated" } }
|
|
48
|
+
|
|
49
|
+
assert_redirected_to board_path(board)
|
|
50
|
+
assert_equal "Updated", board.reload.name
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
test "destroy removes board" do
|
|
54
|
+
board = boards(:writebook)
|
|
55
|
+
|
|
56
|
+
assert_difference -> { Board.count }, -1 do
|
|
57
|
+
delete board_path(board)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
assert_redirected_to boards_path
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Testing Permissions
|
|
66
|
+
|
|
67
|
+
```ruby
|
|
68
|
+
test "non-admin cannot update board" do
|
|
69
|
+
logout_and_sign_in_as :member
|
|
70
|
+
|
|
71
|
+
board = boards(:writebook)
|
|
72
|
+
original_name = board.name
|
|
73
|
+
|
|
74
|
+
patch board_path(board), params: { board: { name: "Hacked" } }
|
|
75
|
+
|
|
76
|
+
assert_response :forbidden
|
|
77
|
+
assert_equal original_name, board.reload.name
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
test "non-member cannot access board" do
|
|
81
|
+
logout_and_sign_in_as :other_user
|
|
82
|
+
|
|
83
|
+
get board_path(boards(:writebook))
|
|
84
|
+
|
|
85
|
+
assert_response :forbidden
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
test "unauthenticated user redirected to login" do
|
|
89
|
+
logout
|
|
90
|
+
|
|
91
|
+
get boards_path
|
|
92
|
+
|
|
93
|
+
assert_redirected_to new_session_path
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
test "owner can destroy board" do
|
|
97
|
+
sign_in_as boards(:writebook).owner
|
|
98
|
+
|
|
99
|
+
assert_difference -> { Board.count }, -1 do
|
|
100
|
+
delete board_path(boards(:writebook))
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
assert_response :redirect
|
|
104
|
+
end
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Testing Response Formats
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
test "responds with HTML" do
|
|
111
|
+
get boards_path
|
|
112
|
+
|
|
113
|
+
assert_response :success
|
|
114
|
+
assert_match "text/html", response.content_type
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
test "responds with JSON" do
|
|
118
|
+
board = boards(:writebook)
|
|
119
|
+
|
|
120
|
+
get board_path(board), as: :json
|
|
121
|
+
|
|
122
|
+
assert_response :success
|
|
123
|
+
assert_match "application/json", response.content_type
|
|
124
|
+
|
|
125
|
+
json = JSON.parse(response.body)
|
|
126
|
+
assert_equal board.name, json["name"]
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
test "responds with Turbo Stream" do
|
|
130
|
+
post boards_path,
|
|
131
|
+
params: { board: { name: "Test" } },
|
|
132
|
+
as: :turbo_stream
|
|
133
|
+
|
|
134
|
+
assert_response :success
|
|
135
|
+
assert_match "text/vnd.turbo-stream.html", response.content_type
|
|
136
|
+
assert_match "turbo-stream", response.body
|
|
137
|
+
end
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Testing Flash Messages
|
|
141
|
+
|
|
142
|
+
```ruby
|
|
143
|
+
test "success flash on create" do
|
|
144
|
+
post boards_path, params: { board: { name: "Test" } }
|
|
145
|
+
|
|
146
|
+
assert_equal "Board created", flash[:notice]
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
test "error flash on invalid create" do
|
|
150
|
+
post boards_path, params: { board: { name: "" } }
|
|
151
|
+
|
|
152
|
+
assert_match /error/, flash[:alert].downcase
|
|
153
|
+
end
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Testing Redirects
|
|
157
|
+
|
|
158
|
+
```ruby
|
|
159
|
+
test "redirects to board after create" do
|
|
160
|
+
post boards_path, params: { board: { name: "Test" } }
|
|
161
|
+
|
|
162
|
+
assert_redirected_to board_path(Board.last)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
test "redirects back with fallback" do
|
|
166
|
+
board = boards(:writebook)
|
|
167
|
+
|
|
168
|
+
patch board_path(board),
|
|
169
|
+
params: { board: { name: "Updated" } },
|
|
170
|
+
headers: { "HTTP_REFERER" => boards_path }
|
|
171
|
+
|
|
172
|
+
assert_redirected_to boards_path
|
|
173
|
+
end
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Testing Parameters
|
|
177
|
+
|
|
178
|
+
```ruby
|
|
179
|
+
test "permits valid parameters" do
|
|
180
|
+
post boards_path, params: {
|
|
181
|
+
board: {
|
|
182
|
+
name: "Test",
|
|
183
|
+
description: "Test description"
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
board = Board.last
|
|
188
|
+
assert_equal "Test", board.name
|
|
189
|
+
assert_equal "Test description", board.description
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
test "filters unpermitted parameters" do
|
|
193
|
+
post boards_path, params: {
|
|
194
|
+
board: {
|
|
195
|
+
name: "Test",
|
|
196
|
+
admin: true # Unpermitted
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
board = Board.last
|
|
201
|
+
assert_equal "Test", board.name
|
|
202
|
+
assert_not board.respond_to?(:admin)
|
|
203
|
+
end
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## System Tests
|
|
209
|
+
|
|
210
|
+
System tests use a real browser (Selenium) to test the full stack.
|
|
211
|
+
|
|
212
|
+
### Basic System Test
|
|
213
|
+
|
|
214
|
+
```ruby
|
|
215
|
+
class CardsTest < ApplicationSystemTestCase
|
|
216
|
+
test "creating a card" do
|
|
217
|
+
sign_in_as users(:david)
|
|
218
|
+
|
|
219
|
+
visit board_url(boards(:writebook))
|
|
220
|
+
click_on "Add a card"
|
|
221
|
+
|
|
222
|
+
fill_in "Title", with: "New feature"
|
|
223
|
+
fill_in "Description", with: "Build something awesome"
|
|
224
|
+
click_on "Create card"
|
|
225
|
+
|
|
226
|
+
assert_selector "h3", text: "New feature"
|
|
227
|
+
assert_text "Build something awesome"
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
test "closing a card" do
|
|
231
|
+
sign_in_as users(:david)
|
|
232
|
+
card = cards(:logo)
|
|
233
|
+
|
|
234
|
+
visit card_url(card)
|
|
235
|
+
click_on "Close"
|
|
236
|
+
|
|
237
|
+
assert_selector ".badge", text: "Closed"
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
test "adding a comment" do
|
|
241
|
+
sign_in_as users(:david)
|
|
242
|
+
card = cards(:logo)
|
|
243
|
+
|
|
244
|
+
visit card_url(card)
|
|
245
|
+
|
|
246
|
+
fill_in "Comment", with: "Great work!"
|
|
247
|
+
click_on "Post comment"
|
|
248
|
+
|
|
249
|
+
assert_text "Great work!"
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Testing JavaScript Interactions
|
|
255
|
+
|
|
256
|
+
```ruby
|
|
257
|
+
test "opening and closing modal", js: true do
|
|
258
|
+
sign_in_as users(:david)
|
|
259
|
+
|
|
260
|
+
visit boards_path
|
|
261
|
+
click_on "New Board"
|
|
262
|
+
|
|
263
|
+
# Modal should appear
|
|
264
|
+
assert_selector "#new-board-modal", visible: true
|
|
265
|
+
|
|
266
|
+
click_on "Cancel"
|
|
267
|
+
|
|
268
|
+
# Modal should disappear
|
|
269
|
+
assert_no_selector "#new-board-modal", visible: true
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
test "auto-save works", js: true do
|
|
273
|
+
sign_in_as users(:david)
|
|
274
|
+
card = cards(:logo)
|
|
275
|
+
|
|
276
|
+
visit edit_card_path(card)
|
|
277
|
+
|
|
278
|
+
fill_in "Title", with: "Auto-saved title"
|
|
279
|
+
|
|
280
|
+
# Wait for auto-save
|
|
281
|
+
assert_text "Saved", wait: 3
|
|
282
|
+
|
|
283
|
+
visit card_path(card)
|
|
284
|
+
assert_text "Auto-saved title"
|
|
285
|
+
end
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### Testing Turbo Frames
|
|
289
|
+
|
|
290
|
+
```ruby
|
|
291
|
+
test "navigating within turbo frame" do
|
|
292
|
+
sign_in_as users(:david)
|
|
293
|
+
|
|
294
|
+
visit boards_path
|
|
295
|
+
|
|
296
|
+
within("#boards-frame") do
|
|
297
|
+
click_on "Show archived"
|
|
298
|
+
assert_text "Archived Boards"
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# Page didn't fully reload
|
|
302
|
+
assert_current_path boards_path
|
|
303
|
+
end
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## Integration Tests
|
|
309
|
+
|
|
310
|
+
Test complete user workflows across multiple requests.
|
|
311
|
+
|
|
312
|
+
```ruby
|
|
313
|
+
class UserFlowTest < ActionDispatch::IntegrationTest
|
|
314
|
+
test "complete card workflow" do
|
|
315
|
+
# Sign in
|
|
316
|
+
sign_in_as :david
|
|
317
|
+
|
|
318
|
+
# Create board
|
|
319
|
+
post boards_path, params: { board: { name: "My Board" } }
|
|
320
|
+
board = Board.last
|
|
321
|
+
|
|
322
|
+
# Create card
|
|
323
|
+
post board_cards_path(board), params: {
|
|
324
|
+
card: { title: "My Card" }
|
|
325
|
+
}
|
|
326
|
+
card = Card.last
|
|
327
|
+
|
|
328
|
+
# Add comment
|
|
329
|
+
post card_comments_path(card), params: {
|
|
330
|
+
comment: { body: "First comment" }
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
# Close card
|
|
334
|
+
post card_closure_path(card)
|
|
335
|
+
|
|
336
|
+
# Verify final state
|
|
337
|
+
assert card.reload.closed?
|
|
338
|
+
assert_equal 1, card.comments.count
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
---
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rails-testing-models
|
|
3
|
+
description: "Rails testing: model tests for associations, validations, scopes, callbacks, and methods"
|
|
4
|
+
group: rails
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Tests Guide
|
|
8
|
+
|
|
9
|
+
Comprehensive guide for Rails testing patterns.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Testing Philosophy
|
|
14
|
+
|
|
15
|
+
1. **Test behavior, not implementation**
|
|
16
|
+
2. **Test happy path and edge cases**
|
|
17
|
+
3. **Test permissions thoroughly**
|
|
18
|
+
4. **Use fixtures for consistent data**
|
|
19
|
+
5. **Keep tests independent**
|
|
20
|
+
6. **Fast tests = tests that get run**
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Test Structure
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
test/
|
|
28
|
+
├── controllers/
|
|
29
|
+
│ ├── boards_controller_test.rb
|
|
30
|
+
│ └── cards/
|
|
31
|
+
│ └── closures_controller_test.rb
|
|
32
|
+
├── models/
|
|
33
|
+
│ ├── card_test.rb
|
|
34
|
+
│ └── card/
|
|
35
|
+
│ └── closeable_test.rb
|
|
36
|
+
├── system/
|
|
37
|
+
│ └── cards_test.rb
|
|
38
|
+
├── integration/
|
|
39
|
+
│ └── user_flow_test.rb
|
|
40
|
+
├── helpers/
|
|
41
|
+
│ └── cards_helper_test.rb
|
|
42
|
+
├── mailers/
|
|
43
|
+
│ └── notification_mailer_test.rb
|
|
44
|
+
├── jobs/
|
|
45
|
+
│ └── notification_job_test.rb
|
|
46
|
+
└── fixtures/
|
|
47
|
+
├── cards.yml
|
|
48
|
+
├── users.yml
|
|
49
|
+
└── boards.yml
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Model Tests
|
|
55
|
+
|
|
56
|
+
### Test File Structure
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
59
|
+
class CardTest < ActiveSupport::TestCase
|
|
60
|
+
# Setup runs before each test
|
|
61
|
+
setup do
|
|
62
|
+
@card = cards(:logo)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Teardown runs after each test (optional)
|
|
66
|
+
teardown do
|
|
67
|
+
# Cleanup if needed
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Test naming: "test description"
|
|
71
|
+
test "belongs to board" do
|
|
72
|
+
assert_equal boards(:writebook), @card.board
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
test "validates title presence when published" do
|
|
76
|
+
card = Card.new(status: :published)
|
|
77
|
+
|
|
78
|
+
assert_not card.valid?
|
|
79
|
+
assert_includes card.errors[:title], "can't be blank"
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Testing Associations
|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
test "belongs to board" do
|
|
88
|
+
card = cards(:logo)
|
|
89
|
+
assert_equal boards(:writebook), card.board
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
test "has many comments" do
|
|
93
|
+
card = cards(:logo)
|
|
94
|
+
assert_respond_to card, :comments
|
|
95
|
+
assert_instance_of ActiveRecord::Associations::CollectionProxy, card.comments
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
test "has many tags through taggings" do
|
|
99
|
+
card = cards(:logo)
|
|
100
|
+
tag = tags(:bug)
|
|
101
|
+
|
|
102
|
+
card.taggings.create!(tag: tag)
|
|
103
|
+
|
|
104
|
+
assert_includes card.tags, tag
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
test "destroys dependent comments" do
|
|
108
|
+
card = cards(:logo)
|
|
109
|
+
comment = card.comments.create!(body: "Test", creator: users(:david))
|
|
110
|
+
|
|
111
|
+
assert_difference -> { Comment.count }, -1 do
|
|
112
|
+
card.destroy
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Testing Validations
|
|
118
|
+
|
|
119
|
+
```ruby
|
|
120
|
+
test "validates presence of title" do
|
|
121
|
+
card = Card.new
|
|
122
|
+
assert_not card.valid?
|
|
123
|
+
assert_includes card.errors[:title], "can't be blank"
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
test "validates uniqueness of number scoped to account" do
|
|
127
|
+
existing = cards(:logo)
|
|
128
|
+
|
|
129
|
+
card = Card.new(
|
|
130
|
+
account: existing.account,
|
|
131
|
+
board: boards(:writebook),
|
|
132
|
+
number: existing.number
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
assert_not card.valid?
|
|
136
|
+
assert_includes card.errors[:number], "has already been taken"
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
test "validates email format" do
|
|
140
|
+
user = User.new(email: "invalid")
|
|
141
|
+
|
|
142
|
+
assert_not user.valid?
|
|
143
|
+
assert_includes user.errors[:email], "is invalid"
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
test "validates conditional presence" do
|
|
147
|
+
card = Card.new(status: :draft)
|
|
148
|
+
assert card.valid? # title not required for draft
|
|
149
|
+
|
|
150
|
+
card.status = :published
|
|
151
|
+
assert_not card.valid?
|
|
152
|
+
assert_includes card.errors[:title], "can't be blank"
|
|
153
|
+
end
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Testing Scopes
|
|
157
|
+
|
|
158
|
+
```ruby
|
|
159
|
+
test "published scope returns published cards" do
|
|
160
|
+
published_card = cards(:logo)
|
|
161
|
+
draft_card = cards(:draft)
|
|
162
|
+
|
|
163
|
+
assert_includes Card.published, published_card
|
|
164
|
+
assert_not_includes Card.published, draft_card
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
test "closed scope returns cards with closure" do
|
|
168
|
+
card = cards(:logo)
|
|
169
|
+
card.close
|
|
170
|
+
|
|
171
|
+
assert_includes Card.closed, card
|
|
172
|
+
assert_not_includes Card.open, card
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
test "parameterized scope filters correctly" do
|
|
176
|
+
user = users(:david)
|
|
177
|
+
card = cards(:logo)
|
|
178
|
+
card.assignments.create!(user: user)
|
|
179
|
+
|
|
180
|
+
assert_includes Card.assigned_to(user), card
|
|
181
|
+
end
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Testing Callbacks
|
|
185
|
+
|
|
186
|
+
```ruby
|
|
187
|
+
test "sets default title before save" do
|
|
188
|
+
card = Card.new(board: boards(:writebook), status: :published)
|
|
189
|
+
card.save!
|
|
190
|
+
|
|
191
|
+
assert_equal "Untitled", card.title
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
test "assigns number on create" do
|
|
195
|
+
card = Card.create!(board: boards(:writebook), title: "Test")
|
|
196
|
+
|
|
197
|
+
assert_not_nil card.number
|
|
198
|
+
assert card.number > 0
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
test "touches board after save" do
|
|
202
|
+
card = cards(:logo)
|
|
203
|
+
board = card.board
|
|
204
|
+
|
|
205
|
+
assert_changes -> { board.reload.updated_at } do
|
|
206
|
+
card.update!(title: "New Title")
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
test "callback only runs under condition" do
|
|
211
|
+
card = cards(:draft)
|
|
212
|
+
|
|
213
|
+
assert_no_changes -> { card.board.reload.updated_at } do
|
|
214
|
+
card.update!(title: "New Title") # Draft cards don't touch board
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Testing Instance Methods
|
|
220
|
+
|
|
221
|
+
```ruby
|
|
222
|
+
test "closed? returns true when closure exists" do
|
|
223
|
+
card = cards(:logo)
|
|
224
|
+
assert_not card.closed?
|
|
225
|
+
|
|
226
|
+
card.close
|
|
227
|
+
assert card.closed?
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
test "move_to changes board and updates events" do
|
|
231
|
+
card = cards(:logo)
|
|
232
|
+
new_board = boards(:other_board)
|
|
233
|
+
|
|
234
|
+
card.move_to(new_board)
|
|
235
|
+
|
|
236
|
+
assert_equal new_board, card.reload.board
|
|
237
|
+
assert_equal new_board.id, card.events.pluck(:board_id).uniq.first
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
test "archive sets archived_at" do
|
|
241
|
+
card = cards(:logo)
|
|
242
|
+
|
|
243
|
+
freeze_time do
|
|
244
|
+
card.archive
|
|
245
|
+
assert_equal Time.current, card.archived_at
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Testing Transactions
|
|
251
|
+
|
|
252
|
+
```ruby
|
|
253
|
+
test "transaction rolls back on error" do
|
|
254
|
+
card = cards(:logo)
|
|
255
|
+
|
|
256
|
+
assert_no_difference "Card::Closure.count" do
|
|
257
|
+
assert_raises(ActiveRecord::RecordInvalid) do
|
|
258
|
+
card.transaction do
|
|
259
|
+
card.create_closure!
|
|
260
|
+
raise ActiveRecord::RecordInvalid # Force rollback
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
assert_not card.closed?
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
test "transaction commits on success" do
|
|
269
|
+
card = cards(:logo)
|
|
270
|
+
|
|
271
|
+
assert_difference -> { card.events.count }, +1 do
|
|
272
|
+
card.close
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
assert card.closed?
|
|
276
|
+
end
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Testing Class Methods
|
|
280
|
+
|
|
281
|
+
```ruby
|
|
282
|
+
test "close_all_stale closes inactive cards" do
|
|
283
|
+
old_card = cards(:logo)
|
|
284
|
+
old_card.update!(last_active_at: 2.months.ago)
|
|
285
|
+
|
|
286
|
+
recent_card = cards(:other)
|
|
287
|
+
recent_card.update!(last_active_at: 1.day.ago)
|
|
288
|
+
|
|
289
|
+
Card.close_all_stale
|
|
290
|
+
|
|
291
|
+
assert old_card.reload.closed?
|
|
292
|
+
assert_not recent_card.reload.closed?
|
|
293
|
+
end
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
---
|