anima-core 0.3.0 → 1.0.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/.reek.yml +27 -1
- data/CHANGELOG.md +4 -0
- data/README.md +219 -25
- data/agents/codebase-analyzer.md +88 -0
- data/agents/codebase-pattern-finder.md +83 -0
- data/agents/documentation-researcher.md +59 -0
- data/agents/thoughts-analyzer.md +102 -0
- data/agents/web-search-researcher.md +71 -0
- data/anima-core.gemspec +3 -0
- data/app/channels/session_channel.rb +76 -28
- data/app/jobs/agent_request_job.rb +24 -0
- data/app/jobs/analytical_brain_job.rb +33 -0
- data/app/jobs/count_event_tokens_job.rb +1 -1
- data/app/models/concerns/event/broadcasting.rb +20 -2
- data/app/models/event.rb +1 -1
- data/app/models/goal.rb +91 -0
- data/app/models/session.rb +347 -22
- data/config/application.rb +2 -0
- data/db/migrate/20260314075248_add_subagent_support_to_sessions.rb +6 -0
- data/db/migrate/20260314112417_add_granted_tools_to_sessions.rb +5 -0
- data/db/migrate/20260314140000_add_name_to_sessions.rb +7 -0
- data/db/migrate/20260314150000_add_viewport_event_ids_to_sessions.rb +7 -0
- data/db/migrate/20260315100000_add_active_skills_to_sessions.rb +7 -0
- data/db/migrate/20260315140843_create_goals.rb +16 -0
- data/db/migrate/20260315144837_add_completed_at_to_goals.rb +5 -0
- data/db/migrate/20260315191105_add_active_workflow_to_sessions.rb +5 -0
- data/lib/agent_loop.rb +65 -9
- data/lib/agents/definition.rb +116 -0
- data/lib/agents/registry.rb +106 -0
- data/lib/analytical_brain/runner.rb +276 -0
- data/lib/analytical_brain/tools/activate_skill.rb +52 -0
- data/lib/analytical_brain/tools/deactivate_skill.rb +43 -0
- data/lib/analytical_brain/tools/deactivate_workflow.rb +34 -0
- data/lib/analytical_brain/tools/everything_is_ready.rb +28 -0
- data/lib/analytical_brain/tools/finish_goal.rb +62 -0
- data/lib/analytical_brain/tools/read_workflow.rb +58 -0
- data/lib/analytical_brain/tools/rename_session.rb +63 -0
- data/lib/analytical_brain/tools/set_goal.rb +60 -0
- data/lib/analytical_brain/tools/update_goal.rb +60 -0
- data/lib/analytical_brain.rb +23 -0
- data/lib/anima/cli/mcp/secrets.rb +76 -0
- data/lib/anima/cli/mcp.rb +197 -0
- data/lib/anima/cli.rb +4 -0
- data/lib/anima/installer.rb +168 -0
- data/lib/anima/settings.rb +226 -0
- data/lib/anima/version.rb +1 -1
- data/lib/anima.rb +9 -0
- data/lib/credential_store.rb +103 -0
- data/lib/environment_probe.rb +232 -0
- data/lib/llm/client.rb +29 -10
- data/lib/mcp/client_manager.rb +86 -0
- data/lib/mcp/config.rb +213 -0
- data/lib/mcp/health_check.rb +77 -0
- data/lib/mcp/secrets.rb +73 -0
- data/lib/mcp/stdio_transport.rb +206 -0
- data/lib/providers/anthropic.rb +8 -7
- data/lib/shell_session.rb +11 -10
- data/lib/skills/definition.rb +97 -0
- data/lib/skills/registry.rb +105 -0
- data/lib/tools/edit.rb +3 -4
- data/lib/tools/mcp_tool.rb +114 -0
- data/lib/tools/read.rb +15 -16
- data/lib/tools/registry.rb +14 -12
- data/lib/tools/request_feature.rb +121 -0
- data/lib/tools/return_result.rb +81 -0
- data/lib/tools/spawn_specialist.rb +109 -0
- data/lib/tools/spawn_subagent.rb +111 -0
- data/lib/tools/subagent_prompts.rb +12 -0
- data/lib/tools/web_get.rb +8 -9
- data/lib/tui/app.rb +332 -43
- data/lib/tui/message_store.rb +20 -0
- data/lib/tui/screens/chat.rb +207 -20
- data/lib/workflows/definition.rb +97 -0
- data/lib/workflows/registry.rb +89 -0
- data/skills/activerecord/SKILL.md +255 -0
- data/skills/activerecord/examples/associations/association_extensions.rb +298 -0
- data/skills/activerecord/examples/associations/basic_associations.rb +118 -0
- data/skills/activerecord/examples/associations/counter_caches.rb +215 -0
- data/skills/activerecord/examples/associations/polymorphic_associations.rb +217 -0
- data/skills/activerecord/examples/associations/self_referential.rb +302 -0
- data/skills/activerecord/examples/associations/through_associations.rb +203 -0
- data/skills/activerecord/examples/basics/crud_operations.rb +209 -0
- data/skills/activerecord/examples/basics/dirty_tracking.rb +218 -0
- data/skills/activerecord/examples/basics/inheritance.rb +377 -0
- data/skills/activerecord/examples/basics/type_casting.rb +317 -0
- data/skills/activerecord/examples/callbacks/alternatives_to_callbacks.rb +447 -0
- data/skills/activerecord/examples/callbacks/conditional_callbacks.rb +353 -0
- data/skills/activerecord/examples/callbacks/lifecycle_callbacks.rb +280 -0
- data/skills/activerecord/examples/callbacks/transaction_callbacks.rb +340 -0
- data/skills/activerecord/examples/migrations/indexes_and_constraints.rb +337 -0
- data/skills/activerecord/examples/migrations/reversible_patterns.rb +403 -0
- data/skills/activerecord/examples/migrations/safe_patterns.rb +420 -0
- data/skills/activerecord/examples/migrations/schema_changes.rb +277 -0
- data/skills/activerecord/examples/querying/batch_processing.rb +226 -0
- data/skills/activerecord/examples/querying/eager_loading.rb +259 -0
- data/skills/activerecord/examples/querying/finder_methods.rb +170 -0
- data/skills/activerecord/examples/querying/optimization.rb +275 -0
- data/skills/activerecord/examples/querying/scopes.rb +260 -0
- data/skills/activerecord/examples/validations/built_in_validators.rb +277 -0
- data/skills/activerecord/examples/validations/conditional_validations.rb +288 -0
- data/skills/activerecord/examples/validations/custom_validators.rb +381 -0
- data/skills/activerecord/examples/validations/database_constraints.rb +432 -0
- data/skills/activerecord/examples/validations/validation_contexts.rb +367 -0
- data/skills/activerecord/references/associations.md +709 -0
- data/skills/activerecord/references/basics.md +622 -0
- data/skills/activerecord/references/callbacks.md +738 -0
- data/skills/activerecord/references/migrations.md +657 -0
- data/skills/activerecord/references/querying.md +655 -0
- data/skills/activerecord/references/validations.md +596 -0
- data/skills/dragonruby/SKILL.md +250 -0
- data/skills/dragonruby/examples/audio/audio_events.rb +55 -0
- data/skills/dragonruby/examples/audio/background_music.rb +29 -0
- data/skills/dragonruby/examples/audio/crossfade.rb +51 -0
- data/skills/dragonruby/examples/audio/music_controls.rb +51 -0
- data/skills/dragonruby/examples/audio/sound_effects.rb +30 -0
- data/skills/dragonruby/examples/core/coordinate_system.rb +27 -0
- data/skills/dragonruby/examples/core/hello_world.rb +24 -0
- data/skills/dragonruby/examples/core/labels.rb +22 -0
- data/skills/dragonruby/examples/core/sprites.rb +35 -0
- data/skills/dragonruby/examples/core/state_management.rb +29 -0
- data/skills/dragonruby/examples/distribution/background_pause.rb +42 -0
- data/skills/dragonruby/examples/distribution/build_workflow.sh +26 -0
- data/skills/dragonruby/examples/distribution/cvars_production.txt +16 -0
- data/skills/dragonruby/examples/distribution/game_metadata_hd.txt +23 -0
- data/skills/dragonruby/examples/distribution/game_metadata_minimal.txt +9 -0
- data/skills/dragonruby/examples/distribution/game_metadata_mobile.txt +31 -0
- data/skills/dragonruby/examples/distribution/platform_detection.rb +36 -0
- data/skills/dragonruby/examples/distribution/steam_metadata.txt +19 -0
- data/skills/dragonruby/examples/entities/collision_detection.rb +43 -0
- data/skills/dragonruby/examples/entities/entity_lifecycle.rb +68 -0
- data/skills/dragonruby/examples/entities/entity_storage.rb +38 -0
- data/skills/dragonruby/examples/entities/factory_methods.rb +45 -0
- data/skills/dragonruby/examples/entities/random_spawning.rb +50 -0
- data/skills/dragonruby/examples/game-logic/reset_patterns.rb +98 -0
- data/skills/dragonruby/examples/game-logic/save_load.rb +101 -0
- data/skills/dragonruby/examples/game-logic/scoring.rb +104 -0
- data/skills/dragonruby/examples/game-logic/state_transitions.rb +103 -0
- data/skills/dragonruby/examples/game-logic/timers.rb +87 -0
- data/skills/dragonruby/examples/input/action_triggers.rb +36 -0
- data/skills/dragonruby/examples/input/analog_movement.rb +28 -0
- data/skills/dragonruby/examples/input/controller_input.rb +28 -0
- data/skills/dragonruby/examples/input/directional_input.rb +24 -0
- data/skills/dragonruby/examples/input/keyboard_input.rb +28 -0
- data/skills/dragonruby/examples/input/mouse_click.rb +26 -0
- data/skills/dragonruby/examples/input/movement_with_bounds.rb +22 -0
- data/skills/dragonruby/examples/input/normalized_movement.rb +32 -0
- data/skills/dragonruby/examples/rendering/frame_animation.rb +32 -0
- data/skills/dragonruby/examples/rendering/labels.rb +32 -0
- data/skills/dragonruby/examples/rendering/layering.rb +51 -0
- data/skills/dragonruby/examples/rendering/solids.rb +61 -0
- data/skills/dragonruby/examples/rendering/sprites.rb +33 -0
- data/skills/dragonruby/examples/rendering/spritesheet_animation.rb +39 -0
- data/skills/dragonruby/examples/scenes/case_dispatch.rb +60 -0
- data/skills/dragonruby/examples/scenes/class_based.rb +150 -0
- data/skills/dragonruby/examples/scenes/pause_overlay.rb +100 -0
- data/skills/dragonruby/examples/scenes/safe_transitions.rb +68 -0
- data/skills/dragonruby/examples/scenes/scene_transitions.rb +98 -0
- data/skills/dragonruby/examples/scenes/send_dispatch.rb +88 -0
- data/skills/dragonruby/references/audio.md +396 -0
- data/skills/dragonruby/references/core.md +385 -0
- data/skills/dragonruby/references/distribution.md +434 -0
- data/skills/dragonruby/references/entities.md +516 -0
- data/skills/dragonruby/references/game-logic/persistence.md +386 -0
- data/skills/dragonruby/references/game-logic/state.md +389 -0
- data/skills/dragonruby/references/input.md +414 -0
- data/skills/dragonruby/references/rendering/animation.md +467 -0
- data/skills/dragonruby/references/rendering/primitives.md +403 -0
- data/skills/dragonruby/references/scenes.md +443 -0
- data/skills/draper-decorators/SKILL.md +344 -0
- data/skills/draper-decorators/examples/application_decorator.rb +61 -0
- data/skills/draper-decorators/examples/decorator_spec.rb +253 -0
- data/skills/draper-decorators/examples/model_decorator.rb +152 -0
- data/skills/draper-decorators/references/anti-patterns.md +640 -0
- data/skills/draper-decorators/references/patterns.md +507 -0
- data/skills/draper-decorators/references/testing.md +559 -0
- data/skills/gh-issue.md +182 -0
- data/skills/mcp-server/SKILL.md +177 -0
- data/skills/mcp-server/examples/dynamic_tools.rb +36 -0
- data/skills/mcp-server/examples/file_manager_tool.rb +85 -0
- data/skills/mcp-server/examples/http_client.rb +48 -0
- data/skills/mcp-server/examples/http_server.rb +97 -0
- data/skills/mcp-server/examples/rails_integration.rb +88 -0
- data/skills/mcp-server/examples/stdio_server.rb +108 -0
- data/skills/mcp-server/examples/streaming_client.rb +95 -0
- data/skills/mcp-server/references/gotchas.md +183 -0
- data/skills/mcp-server/references/prompts.md +98 -0
- data/skills/mcp-server/references/resources.md +53 -0
- data/skills/mcp-server/references/server.md +140 -0
- data/skills/mcp-server/references/tools.md +146 -0
- data/skills/mcp-server/references/transport.md +104 -0
- data/skills/ratatui-ruby/SKILL.md +315 -0
- data/skills/ratatui-ruby/references/core-concepts.md +340 -0
- data/skills/ratatui-ruby/references/events.md +387 -0
- data/skills/ratatui-ruby/references/frameworks.md +522 -0
- data/skills/ratatui-ruby/references/layout.md +423 -0
- data/skills/ratatui-ruby/references/styling.md +268 -0
- data/skills/ratatui-ruby/references/testing.md +433 -0
- data/skills/ratatui-ruby/references/widgets.md +532 -0
- data/skills/rspec/SKILL.md +340 -0
- data/skills/rspec/examples/core/basic_structure.rb +69 -0
- data/skills/rspec/examples/core/configuration.rb +126 -0
- data/skills/rspec/examples/core/hooks.rb +126 -0
- data/skills/rspec/examples/core/memoized_helpers.rb +139 -0
- data/skills/rspec/examples/core/metadata_filtering.rb +144 -0
- data/skills/rspec/examples/core/shared_examples.rb +145 -0
- data/skills/rspec/examples/factory_bot/associations.rb +314 -0
- data/skills/rspec/examples/factory_bot/build_strategies.rb +272 -0
- data/skills/rspec/examples/factory_bot/callbacks.rb +320 -0
- data/skills/rspec/examples/factory_bot/custom_construction.rb +328 -0
- data/skills/rspec/examples/factory_bot/factory_definition.rb +191 -0
- data/skills/rspec/examples/factory_bot/inheritance.rb +314 -0
- data/skills/rspec/examples/factory_bot/traits.rb +293 -0
- data/skills/rspec/examples/factory_bot/transients.rb +229 -0
- data/skills/rspec/examples/matchers/change.rb +115 -0
- data/skills/rspec/examples/matchers/collections.rb +154 -0
- data/skills/rspec/examples/matchers/comparisons.rb +79 -0
- data/skills/rspec/examples/matchers/composing.rb +155 -0
- data/skills/rspec/examples/matchers/custom_matchers.rb +197 -0
- data/skills/rspec/examples/matchers/equality.rb +58 -0
- data/skills/rspec/examples/matchers/errors.rb +136 -0
- data/skills/rspec/examples/matchers/output.rb +103 -0
- data/skills/rspec/examples/matchers/predicates.rb +87 -0
- data/skills/rspec/examples/matchers/truthiness.rb +101 -0
- data/skills/rspec/examples/matchers/types.rb +82 -0
- data/skills/rspec/examples/matchers/yield.rb +147 -0
- data/skills/rspec/examples/mocks/any_instance.rb +172 -0
- data/skills/rspec/examples/mocks/argument_matchers.rb +206 -0
- data/skills/rspec/examples/mocks/constants.rb +177 -0
- data/skills/rspec/examples/mocks/doubles.rb +139 -0
- data/skills/rspec/examples/mocks/expectations.rb +137 -0
- data/skills/rspec/examples/mocks/message_chains.rb +173 -0
- data/skills/rspec/examples/mocks/ordering.rb +144 -0
- data/skills/rspec/examples/mocks/receive_counts.rb +181 -0
- data/skills/rspec/examples/mocks/responses.rb +223 -0
- data/skills/rspec/examples/mocks/spies.rb +149 -0
- data/skills/rspec/examples/mocks/stubbing.rb +133 -0
- data/skills/rspec/examples/rails/channels.rb +250 -0
- data/skills/rspec/examples/rails/controller_specs.rb +302 -0
- data/skills/rspec/examples/rails/helper_specs.rb +245 -0
- data/skills/rspec/examples/rails/job_specs.rb +256 -0
- data/skills/rspec/examples/rails/mailer_specs.rb +228 -0
- data/skills/rspec/examples/rails/matchers.rb +374 -0
- data/skills/rspec/examples/rails/model_specs.rb +193 -0
- data/skills/rspec/examples/rails/request_specs.rb +275 -0
- data/skills/rspec/examples/rails/routing_specs.rb +276 -0
- data/skills/rspec/examples/rails/system_specs.rb +294 -0
- data/skills/rspec/examples/rails/transactions.rb +254 -0
- data/skills/rspec/examples/rails/view_specs.rb +252 -0
- data/skills/rspec/references/core.md +816 -0
- data/skills/rspec/references/factory_bot.md +641 -0
- data/skills/rspec/references/matchers.md +516 -0
- data/skills/rspec/references/mocks.md +381 -0
- data/skills/rspec/references/rails.md +528 -0
- data/templates/soul.md +40 -0
- data/workflows/commit.md +45 -0
- data/workflows/create_handoff.md +98 -0
- data/workflows/create_note.md +82 -0
- data/workflows/create_plan.md +457 -0
- data/workflows/decompose_ticket.md +109 -0
- data/workflows/feature.md +91 -0
- data/workflows/implement_plan.md +87 -0
- data/workflows/iterate_plan.md +247 -0
- data/workflows/research_codebase.md +210 -0
- data/workflows/resume_handoff.md +217 -0
- data/workflows/review_pr.md +320 -0
- data/workflows/thoughts_init.md +71 -0
- data/workflows/validate_plan.md +166 -0
- metadata +284 -1
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# RSpec Core: Memoized Helpers Examples
|
|
2
|
+
# Source: rspec-core gem features/helper_methods/let.feature
|
|
3
|
+
|
|
4
|
+
# let - lazy evaluation and memoization
|
|
5
|
+
RSpec.describe "let behavior" do
|
|
6
|
+
let(:user) { build(:user) }
|
|
7
|
+
|
|
8
|
+
it "memoizes within an example" do
|
|
9
|
+
expect(user).to be(user) # Same object instance
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it "creates fresh instance for each example" do
|
|
13
|
+
expect(user.object_id).not_to eq(0) # New user each time
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# let! - eager evaluation (before hook)
|
|
18
|
+
RSpec.describe "let! behavior" do
|
|
19
|
+
let!(:user) { create(:user) }
|
|
20
|
+
|
|
21
|
+
it "creates user before example runs" do
|
|
22
|
+
# User already exists in database
|
|
23
|
+
expect(User.count).to eq(1)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# When to use let vs let!
|
|
28
|
+
RSpec.describe User do
|
|
29
|
+
# Use let - value referenced in test body
|
|
30
|
+
let(:user) { build(:user) }
|
|
31
|
+
|
|
32
|
+
it "validates email format" do
|
|
33
|
+
user.email = "invalid"
|
|
34
|
+
expect(user).not_to be_valid
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
RSpec.describe User, ".active scope" do
|
|
39
|
+
# Use let! - records must exist for database query
|
|
40
|
+
let!(:active_user) { create(:user, status: :active) }
|
|
41
|
+
let!(:inactive_user) { create(:user, status: :inactive) }
|
|
42
|
+
|
|
43
|
+
it "returns only active users" do
|
|
44
|
+
expect(User.active).to contain_exactly(active_user)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Overriding let in nested contexts
|
|
49
|
+
RSpec.describe ShoppingCart do
|
|
50
|
+
subject(:cart) { build(:shopping_cart, items: [item], discount:) }
|
|
51
|
+
|
|
52
|
+
let(:discount) { 0 }
|
|
53
|
+
let(:item) { build(:item, price: 100) }
|
|
54
|
+
|
|
55
|
+
it "calculates full price" do
|
|
56
|
+
expect(cart.total).to eq(100)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
context "with discount" do
|
|
60
|
+
let(:discount) { 20 } # Override parent definition
|
|
61
|
+
|
|
62
|
+
it "applies discount" do
|
|
63
|
+
expect(cart.total).to eq(80)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Using super() to extend parent let
|
|
69
|
+
RSpec.describe "API request" do
|
|
70
|
+
let(:params) { { name: "Item", price: 10 } }
|
|
71
|
+
|
|
72
|
+
context "with discount" do
|
|
73
|
+
let(:params) { super().merge(discount: 2) } # Extend parent
|
|
74
|
+
|
|
75
|
+
it "includes discount in params" do
|
|
76
|
+
expect(params).to eq(name: "Item", price: 10, discount: 2)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Named subject - always use named subjects
|
|
82
|
+
RSpec.describe Article do
|
|
83
|
+
subject(:article) { build(:article, title: "Hello") }
|
|
84
|
+
|
|
85
|
+
it "validates presence of body" do
|
|
86
|
+
expect(article).not_to be_valid
|
|
87
|
+
expect(article.errors[:body]).to include("can't be blank")
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Subject for method testing
|
|
92
|
+
RSpec.describe Calculator do
|
|
93
|
+
subject(:calculator) { build(:calculator) }
|
|
94
|
+
|
|
95
|
+
describe "#add" do
|
|
96
|
+
subject(:result) { calculator.add(2, 3) }
|
|
97
|
+
|
|
98
|
+
it { is_expected.to eq(5) }
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Subject placement - always first in example group
|
|
103
|
+
RSpec.describe UserSerializer do
|
|
104
|
+
subject(:serializer) { described_class.new(user) } # First
|
|
105
|
+
let(:user) { create(:user, name: "Alice") } # After subject
|
|
106
|
+
|
|
107
|
+
it "serializes name" do
|
|
108
|
+
expect(serializer.as_json[:name]).to eq("Alice")
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Anti-pattern: let! when let suffices
|
|
113
|
+
RSpec.describe "Unnecessary let!" do
|
|
114
|
+
# BAD - creates data unnecessarily
|
|
115
|
+
# let!(:admin) { create(:user, :admin) }
|
|
116
|
+
|
|
117
|
+
# GOOD - use let, only creates when referenced
|
|
118
|
+
let(:admin) { create(:user, :admin) }
|
|
119
|
+
|
|
120
|
+
it "validates email format" do
|
|
121
|
+
user = build(:user, email: "invalid")
|
|
122
|
+
expect(user).not_to be_valid
|
|
123
|
+
# admin never used, never created
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Helper methods for complex setup
|
|
128
|
+
RSpec.describe Order do
|
|
129
|
+
def create_order_with_items(count)
|
|
130
|
+
order = build(:order)
|
|
131
|
+
count.times { order.add_item(build(:item)) }
|
|
132
|
+
order
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
it "calculates total for multiple items" do
|
|
136
|
+
order = create_order_with_items(3)
|
|
137
|
+
expect(order.items.count).to eq(3)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# RSpec Core: Metadata and Filtering Examples
|
|
2
|
+
# Source: rspec-core gem features/filtering/*.feature
|
|
3
|
+
|
|
4
|
+
# Adding metadata to examples
|
|
5
|
+
RSpec.describe "API" do
|
|
6
|
+
it "handles fast requests", :fast do
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
it "handles slow requests", :slow, timeout: 30 do
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it "requires authentication", authorized: true do
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Adding metadata to groups
|
|
17
|
+
RSpec.describe PaymentGateway, :integration, :external do
|
|
18
|
+
it "processes payment" do
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Accessing metadata in examples
|
|
23
|
+
RSpec.describe "Metadata access" do
|
|
24
|
+
it "can read its own metadata" do |example|
|
|
25
|
+
expect(example.metadata[:description]).to eq("can read its own metadata")
|
|
26
|
+
expect(example.metadata[:file_path]).to include("_spec.rb")
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Focus filtering - run only focused examples
|
|
31
|
+
RSpec.configure do |config|
|
|
32
|
+
config.filter_run_when_matching :focus
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
RSpec.describe "Debugging" do
|
|
36
|
+
it "skipped example" do
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
fit "focused example - only this runs" do # fit = it with :focus
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "another skipped example" do
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Exclusion filtering
|
|
47
|
+
RSpec.configure do |config|
|
|
48
|
+
config.filter_run_excluding :slow
|
|
49
|
+
config.filter_run_excluding broken: true
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
RSpec.describe "Suite" do
|
|
53
|
+
it "runs normally" do
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it "skipped - marked slow", :slow do
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it "skipped - marked broken", broken: true do
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Skip and pending
|
|
64
|
+
RSpec.describe "Skip examples" do
|
|
65
|
+
xit "skipped with xit" do
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it "skipped with skip call" do
|
|
69
|
+
skip "Not implemented yet"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it "skipped via metadata", skip: "Waiting on API" do
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
RSpec.describe "Pending examples" do
|
|
77
|
+
it "pending - expected to fail" do
|
|
78
|
+
pending "Waiting for feature X"
|
|
79
|
+
expect(1 + 1).to eq(3) # Fails, but marked pending
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# If pending test passes, RSpec fails it (unexpected pass)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Conditional hooks with metadata
|
|
86
|
+
RSpec.configure do |config|
|
|
87
|
+
config.before(:example, :db) do
|
|
88
|
+
DatabaseCleaner.start
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
config.after(:example, :db) do
|
|
92
|
+
DatabaseCleaner.clean
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
config.around(:example, :vcr) do |example|
|
|
96
|
+
VCR.use_cassette(example.metadata[:description]) do
|
|
97
|
+
example.run
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
RSpec.describe UserService, :db do
|
|
103
|
+
it "creates user" do # DatabaseCleaner runs automatically
|
|
104
|
+
expect { UserService.create(name: "Alice") }.to change(User, :count).by(1)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
RSpec.describe ExternalAPI, :vcr do
|
|
109
|
+
it "fetches data" do # VCR cassette used automatically
|
|
110
|
+
response = ExternalAPI.fetch
|
|
111
|
+
expect(response).to be_success
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Aggregate failures - continue after first failure
|
|
116
|
+
RSpec.describe "Response validation" do
|
|
117
|
+
it "validates all response fields" do
|
|
118
|
+
response = Client.make_request
|
|
119
|
+
|
|
120
|
+
aggregate_failures "response validation" do
|
|
121
|
+
expect(response.status).to eq(200)
|
|
122
|
+
expect(response.headers).to include("Content-Type" => "application/json")
|
|
123
|
+
expect(response.body).to include("success")
|
|
124
|
+
# All expectations run, failures collected
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Aggregate failures via metadata
|
|
130
|
+
RSpec.describe Client, :aggregate_failures do
|
|
131
|
+
it "validates response" do
|
|
132
|
+
response = Client.make_request
|
|
133
|
+
expect(response.status).to eq(200)
|
|
134
|
+
expect(response.body).to include("success")
|
|
135
|
+
# Both run even if first fails
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Global aggregate failures
|
|
140
|
+
RSpec.configure do |config|
|
|
141
|
+
config.define_derived_metadata do |meta|
|
|
142
|
+
meta[:aggregate_failures] = true
|
|
143
|
+
end
|
|
144
|
+
end
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# RSpec Core: Shared Examples & Shared Context
|
|
2
|
+
# Source: rspec-core gem features/example_groups/shared_examples.feature
|
|
3
|
+
|
|
4
|
+
# shared_examples - reusable test assertions
|
|
5
|
+
RSpec.shared_examples "a collection" do
|
|
6
|
+
let(:collection) { build(:collection, items: [7, 2, 4]) }
|
|
7
|
+
|
|
8
|
+
context "initialized with 3 items" do
|
|
9
|
+
it "reports correct size" do
|
|
10
|
+
expect(collection.size).to eq(3)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
describe "#include?" do
|
|
15
|
+
context "with item in collection" do
|
|
16
|
+
it "returns true" do
|
|
17
|
+
expect(collection.include?(7)).to be(true)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
context "with item not in collection" do
|
|
22
|
+
it "returns false" do
|
|
23
|
+
expect(collection.include?(9)).to be(false)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
RSpec.describe CustomArray do
|
|
30
|
+
it_behaves_like "a collection"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
RSpec.describe CustomSet do
|
|
34
|
+
it_behaves_like "a collection"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Passing data via positional parameters
|
|
38
|
+
RSpec.shared_examples "measurable" do |expected_size|
|
|
39
|
+
it "has size #{expected_size}" do
|
|
40
|
+
expect(subject.size).to eq(expected_size)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
RSpec.describe Bucket do
|
|
45
|
+
subject(:bucket) { build(:bucket, :with_items, items_count: 3) }
|
|
46
|
+
|
|
47
|
+
it_behaves_like "measurable", 3
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Passing data via keyword arguments (recommended)
|
|
51
|
+
RSpec.shared_examples "validatable" do |required: [], optional: []|
|
|
52
|
+
required.each do |attr|
|
|
53
|
+
it "validates presence of #{attr}" do
|
|
54
|
+
subject.send("#{attr}=", nil)
|
|
55
|
+
expect(subject).not_to be_valid
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
RSpec.describe User do
|
|
61
|
+
subject(:user) { build(:user) }
|
|
62
|
+
|
|
63
|
+
it_behaves_like "validatable", required: [:email, :name]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Passing data via block (runtime context)
|
|
67
|
+
RSpec.shared_examples "a container" do
|
|
68
|
+
it "is not empty" do
|
|
69
|
+
expect(collection).not_to be_empty
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
RSpec.describe "custom container" do
|
|
74
|
+
it_behaves_like "a container" do
|
|
75
|
+
let(:collection) { build_list(:item, 3) } # Defined at runtime
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# shared_context - reusable setup (no assertions)
|
|
80
|
+
RSpec.shared_context "authenticated user" do
|
|
81
|
+
let(:current_user) { create(:user) }
|
|
82
|
+
|
|
83
|
+
before do
|
|
84
|
+
sign_in(current_user)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
RSpec.describe DashboardController do
|
|
89
|
+
include_context "authenticated user"
|
|
90
|
+
|
|
91
|
+
it "shows dashboard" do
|
|
92
|
+
get :index
|
|
93
|
+
expect(response).to be_successful
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# shared_context with metadata auto-inclusion
|
|
98
|
+
RSpec.shared_context "with admin", :admin do
|
|
99
|
+
let(:current_user) { create(:user, :admin) }
|
|
100
|
+
|
|
101
|
+
before { sign_in(current_user) }
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
RSpec.configure do |config|
|
|
105
|
+
config.include_context "with admin", :admin
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
RSpec.describe AdminController, :admin do
|
|
109
|
+
it "allows admin access" do # Context auto-included
|
|
110
|
+
get :index
|
|
111
|
+
expect(response).to be_successful
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# it_behaves_like vs include_examples
|
|
116
|
+
# PREFER it_behaves_like - creates nested context, safe
|
|
117
|
+
RSpec.describe Controller do
|
|
118
|
+
it_behaves_like "user actions", :admin # Nested: "behaves like user actions"
|
|
119
|
+
it_behaves_like "user actions", :regular # Separate context, no conflicts
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# AVOID include_examples multiple times - method conflicts
|
|
123
|
+
# describe Controller do
|
|
124
|
+
# include_examples "user actions", :admin
|
|
125
|
+
# include_examples "user actions", :regular # BAD: Overrides let definitions!
|
|
126
|
+
# end
|
|
127
|
+
|
|
128
|
+
# When to use shared examples
|
|
129
|
+
# GOOD: Interface compliance testing
|
|
130
|
+
RSpec.shared_examples "timestamped model" do
|
|
131
|
+
it { is_expected.to respond_to(:created_at) }
|
|
132
|
+
it { is_expected.to respond_to(:updated_at) }
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
RSpec.describe User do
|
|
136
|
+
subject(:user) { build(:user) }
|
|
137
|
+
|
|
138
|
+
it_behaves_like "timestamped model"
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
RSpec.describe Post do
|
|
142
|
+
subject(:post) { build(:post) }
|
|
143
|
+
|
|
144
|
+
it_behaves_like "timestamped model"
|
|
145
|
+
end
|