.vbw-planning/milestones/07-rails-audit-and-refactoring/06-test-infrastructure/.context-dev.md
Not available
Codebase mapping exists in .vbw-planning/codebase/. Key files:
ARCHITECTURE.mdCONCERNS.mdPATTERNS.mdDEPENDENCIES.mdSTRUCTURE.mdCONVENTIONS.mdTESTING.mdSTACK.mdRead CONVENTIONS.md, PATTERNS.md, STRUCTURE.md, and DEPENDENCIES.md first to bootstrap codebase understanding.
.vbw-planning/config.json.vbw-planning/milestones/03-coverage-analysis-quick-wins-critical-path-test-co/STATE.md.vbw-planning/phases/02-feed-reliability/01-SUMMARY.md.vbw-planning/phases/02-feed-reliability/02-SUMMARY.md.vbw-planning/phases/02-feed-reliability/03-SUMMARY.md.vbw-planning/phases/02-feed-reliability/04-SUMMARY.md.vbw-planning/phases/03-dashboard-pagination/03-04-SUMMARY.md.vbw-planning/phases/05-simplify-source-status/01-SUMMARY.md.vbw-planning/phases/05-simplify-source-status/03-SUMMARY.md.vbw-planning/phases/05-simplify-source-status/05-SUMMARY.md.vbw-planning/ROADMAP.mdtest/dummy/db/schema.rbtest/dummy/Gemfile.lock.vbw-planning/config.json (51 lines, first 30 shown){
"effort": "thorough",
"autonomy": "standard",
"auto_commit": true,
"planning_tracking": "manual",
"auto_push": "never",
"verification_tier": "standard",
"skill_suggestions": true,
"auto_install_skills": false,
"discovery_questions": true,
"context_compiler": true,
"visual_format": "unicode",
"max_tasks_per_plan": 5,
"prefer_teams": "auto",
"branch_per_milestone": false,
"plain_summary": true,
"active_profile": "default",
"custom_profiles": {},
"model_profile": "quality",
"model_overrides": {},
"agent_max_turns": {
"scout": 15,
"qa": 25,
"architect": 30,
"debugger": 80,
"lead": 50,
"dev": 75
},
"qa_skip_agents": [
"docs"
.vbw-planning/milestones/03-coverage-analysis-quick-wins-critical-path-test-co/STATE.md (64 lines, first 30 shown)<!-- VBW STATE TEMPLATE (ARTF-05) -- Session dashboard, auto-updated -->
<!-- Updated after each plan completion and at checkpoints -->
# Project State
## Project Reference
See: .vbw-planning/PROJECT.md (updated 2026-02-09)
**Core value:** Drop-in Rails engine for feed monitoring, content scraping, and operational dashboards.
**Current focus:** All phases complete
## Current Position
Phase: 4 of 4 (Code Quality & Conventions Cleanup)
Plan: 3 of 3 in current phase
Status: Built
Last activity: 2026-02-10 -- Phase 4 complete (3/3 plans done)
Progress: [##########] 100%
## Codebase Profile
- **Total source files:** 535
- **Primary language:** Ruby (330 files)
- **Templates:** ERB (48 files)
- **Tests:** 137 test files detected
- **Test suite:** 841 runs, 2776 assertions, 0 failures (up from 473 runs in Phase 1)
- **Coverage:** 86.97% line, 58.84% branch (510 uncovered lines, down from 2117)
- **CI/CD:** GitHub Actions (1 workflow)
.vbw-planning/ROADMAP.md (131 lines, first 30 shown)# Roadmap
**Milestone:** rails-audit-and-modal
## Phases
- [x] Phase 01: Quick Wins & Security
- [x] Phase 02: Model Layer Hardening
- [x] Phase 03: Controller & Route Refactoring
- [x] Phase 04: Job & Pipeline Reliability
- [x] Phase 05: View Layer Extraction
- [ ] Phase 06: Test Infrastructure
- [ ] Phase 07: Ultimate Turbo Modal Integration
## Phase Details
### Phase 01: Quick Wins & Security
**Goal:** Fix high-priority security issues and low-effort quick wins from the Rails audit (findings M1, M3, M5, M12, C6, C9, V2, V8, V10, V11).
**Success Criteria:**
- ImportHistoryDismissalsController scoped to current user
- DashboardController uses explicit parameter allowlist (no .permit!)
- ScrapeLog has by_source, by_status, by_item scopes
- Scrape status badge logic extracted to helper
- MutationObserver disconnect leak fixed
**Plans:**
- [x] Plan 01: Security & Controller Fixes
- [x] Plan 02: Model & Job Fixes
test/dummy/db/schema.rb (449 lines, first 30 shown)# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# This file is the source Rails uses to define your schema when running `bin/rails
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
# be faster and is potentially less error prone than running all of your
# migrations from scratch. Old migrations may fail to apply correctly if those
# migrations use external dependencies or application code.
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.1].define(version: 2026_03_13_120000) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_catalog.plpgsql"
create_table "active_storage_attachments", force: :cascade do |t|
t.bigint "blob_id", null: false
t.datetime "created_at", null: false
t.string "name", null: false
t.bigint "record_id", null: false
t.string "record_type", null: false
t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
end
create_table "active_storage_blobs", force: :cascade do |t|
t.bigint "byte_size", null: false
t.string "checksum"
t.string "content_type"
test/dummy/Gemfile.lock (409 lines, first 30 shown)PATH
remote: ../..
specs:
source_monitor (0.11.1)
cssbundling-rails (~> 1.4)
faraday (~> 2.9)
faraday-follow_redirects (~> 0.4)
faraday-gzip (~> 3.0)
faraday-retry (~> 2.2)
feedjira (>= 3.2, < 5.0)
jsbundling-rails (~> 1.3)
nokolexbor (~> 0.5)
rails (>= 8.0.3, < 10.0)
ransack (~> 4.2)
ruby-readability (~> 0.7)
solid_cable (>= 3.0, < 4.0)
solid_queue (>= 0.3, < 3.0)
turbo-rails (~> 2.0)
GEM
remote: https://rubygems.org/
specs:
action_text-trix (2.1.16)
railties
actioncable (8.1.2)
actionpack (= 8.1.2)
activesupport (= 8.1.2)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6)
Framework & Setup:
test/dummy/ with Solid Queue and HotwireKey Files:
test/test_helper.rb - Main test setup (65-128 lines)test/test_prof.rb - TestProf integration (38 lines)test/support/host_app_harness.rb - Host app test generation (395 lines)test/vcr_cassettes/source_monitor/ (4 cassettes found)Test Count by Directory (estimated):
Test Count by Type:
SourceMonitor.reset_configuration! + SourceMonitor.config.http.retry_max = 0Finding: VCR is configured but usage patterns are not standardized across tests.
Current State:
test/test_helper.rb (lines 59-63):
test/vcr_cassettes/ignore_localhost = truesource_monitor/fetching/rss_success.ymlsource_monitor/fetching/atom_success.ymlsource_monitor/fetching/json_success.ymlsource_monitor/fetching/netflix_medium_rss.ymlVCR.use_cassette calls found in test files (no grep matches)Gap: Cassette strategy is not documented:
source_monitor/<module>/<descriptor>)Files Affected: test/test_helper.rb, test/vcr_cassettes/
Finding: Test helper fixtures :all loads all fixtures, creating implicit dependencies.
Current State:
test/test_helper.rb (lines 51-57):
if ActiveSupport::TestCase.respond_to?(:fixture_paths=)
fixtures_root = File.expand_path("fixtures", __dir__)
ActiveSupport::TestCase.fixture_paths = [ fixtures_root ]
ActionDispatch::IntegrationTest.fixture_paths = ActiveSupport::TestCase.fixture_paths
ActiveSupport::TestCase.file_fixture_path = fixtures_root
ActiveSupport::TestCase.fixtures :all # <-- loads ALL fixtures
end
fixtures :all loads every fixture, including users.yml if it existsfixtures :users found in grep (search returned no matches)fixtures :all implicitly couples every test to user model if fixtures existGap:
Files Affected: test/test_helper.rb, test/fixtures/ (directory)
Finding: Refactored sub-modules lack dedicated unit test files.
Current State:
AdaptiveInterval - no dedicated test file foundSourceUpdater - no dedicated test file foundEntryProcessor - test file exists: test/lib/source_monitor/fetching/feed_fetcher/entry_processor_test.rbEntryProcessor has 1 test fileGap:
AdaptiveInterval and SourceUpdater behavior don't existFiles Affected:
lib/source_monitor/fetching/feed_fetcher/adaptive_interval.rb (no test file)lib/source_monitor/fetching/feed_fetcher/source_updater.rb (no test file)lib/source_monitor/fetching/feed_fetcher/entry_processor.rb (test exists)Finding: Integration tests exist but coverage is incomplete for complex flows.
Current State:
test/integration/engine_mounting_test.rbtest/integration/host_install_flow_test.rbtest/integration/release_packaging_test.rbtest/integration/navigation_test.rb (4 total)Gap:
Estimated Test Count Needed: 8-12 additional integration tests
Finding: System tests test success flows but avoid error scenarios.
Current State:
test/system/dashboard_test.rbtest/system/items_test.rbtest/system/logs_test.rbtest/system/mission_control_test.rbtest/system/sources_test.rbtest/system/dropdown_fallback_test.rbGap:
Example Missing Test: "source deletion confirmation shows error if deletion fails"
Finding: Factory-like helpers are defined locally in each test file instead of centrally.
Current State:
test/test_helper.rb: only create_source! (lines 107-119)test/lib/source_monitor/items/retention_pruner_test.rb:
build_source (lines 147-155)create_item (lines 157-165)Pattern: Each test file that needs item/source/log creation defines its own helper, e.g.:
# In retention_pruner_test.rb
def build_source(attributes = {})
defaults = { name: "Source #{SecureRandom.hex(4)}", ... }
create_source!(defaults.merge(attributes))
end
def create_item(source:, guid:, published_at:, title:)
source.items.create!(guid:, url: "...", title:, ...)
end
Gap:
Files Likely Duplicating:
Item.create!, Source.create!, ScrapeLog.create!, FetchLog.create!, etc.Finding: Tests use 3+ different mocking styles without standardization.
Current State:
.stub):
# test/lib/source_monitor/fetching/advisory_lock_test.rb:40
ActiveRecord::Base.connection_pool.stub :with_connection, ->(&block) { block.call(fake_connection) } do
assert_raises(...)
end
# test/lib/source_monitor/fetching/advisory_lock_test.rb:30-37
fake_connection = Class.new do
def exec_query(sql)
# mock behavior
end
end.new
# test/system/items_test.rb:99
SourceMonitor::Scrapers::Readability.stub(:call, result) do
# test code
end
Gap:
Finding: Some tests create large numbers of WebMock stubs inline.
Current State:
test/test_helper.rb (lines 65): WebMock disables all external HTTP except localhostGap:
stub_request callsFinding: System tests define setup/teardown per-file instead of in a base class.
Current State:
application_system_test_case.rb (no file found yet)# test/system/dashboard_test.rb:9-23
def setup
super
SourceMonitor.reset_configuration!
SourceMonitor::Jobs::Visibility.reset!
SourceMonitor::Jobs::Visibility.setup!
purge_solid_queue_tables
SourceMonitor::Dashboard::TurboBroadcaster.setup!
end
def teardown
SourceMonitor.reset_configuration!
SourceMonitor::Jobs::Visibility.reset!
purge_solid_queue_tables
super
end
purge_solid_queue_tables (lines 167-184, dashboard_test.rb)seed_queue_activity (lines 140-165, dashboard_test.rb)apply_turbo_stream_messages (lines 186-226, dashboard_test.rb)connect_turbo_cable_stream_sources (not shown but referenced)capture_turbo_stream_broadcasts (not shown but referenced)parse_turbo_streams (lines 229-245, dashboard_test.rb)assert_item_order (test/system/items_test.rb:160-168)Gap:
Files Affected: All system test files (test/system/*.rb)
Finding: Controller tests focus on happy paths; error scenarios are light.
Current State:
test/controllers/source_monitor/application_controller_test.rb (55 lines, 4 tests)test/controllers/source_monitor/source_fetches_controller_test.rb (44 lines, 2 tests)test/controllers/source_monitor/health_controller_test.rbtest/controllers/source_monitor/logs_controller_test.rbtest/controllers/source_monitor/source_bulk_scrapes_controller_test.rbtest/controllers/source_monitor/source_health_checks_controller_test.rbtest "queues a fetch and renders turbo stream" do
source = create_source!(fetch_status: "idle")
assert_enqueued_jobs 1 do
post source_monitor.source_fetch_path(source), as: :turbo_stream
end
assert_response :success
end
Gap:
Finding: Tests use travel_to but assertions don't always validate the time-based behavior.
Current State:
test/lib/source_monitor/items/retention_pruner_test.rb uses travel_to:
test "removes items older than retention period" do
travel_to Time.zone.local(2025, 10, 1, 12, 0, 0) do
create_item(...) # old item created
end
travel_to Time.zone.local(2025, 10, 10, 12, 0, 0) do
create_item(...) # recent item created
result = RetentionPruner.call(source:)
assert_equal 1, result.removed_by_age # validates deletion
end
end
travel_back ensures (appear to be present)Gap:
ensure travel_back enforcer in base classFinding: Job testing uses both :test and :inline adapters without clear convention.
Current State:
:test (test_helper.rb:48)
ActiveJob::Base.queue_adapter = :test
with_inline_jobs when execution needed:
# test/system/items_test.rb:101
with_inline_jobs do
click_button "Manual Scrape"
end
assert_enqueued_with for :test adapterGap:
with_inline_jobs suggests it should be in test_helper but it's in test_prof.rbFinding: Counter cache fields (items_count on Source) are not validated for atomicity.
Current State:
counter_cache: true on has_many :items# test/system/items_test.rb:88
initial_item_count = Item.count
item = Item.create!(...)
# Later assertion checks the count
Gap:
reset_items_counter! testingFinding: Test names vary in style and specificity.
Current State:
Gap:
Finding: Concerns (Loggable, etc.) are tested inline; no shared test modules.
Current State:
Loggable concern used by FetchLog, ScrapeLog, HealthCheckLogtest/lib/source_monitor/logs/entry_sync_test.rb tests log behavior directlyGap:
Finding: ApplicationSystemTestCase doesn't exist or lacks Capybara wait config.
Current State:
test/dummy/app/test/application_system_test_case.rb filewait: 5, wait: 10, visible: :all, wait: 5# test/system/dashboard_test.rb:86
assert_selector "turbo-cable-stream-source", visible: :all, wait: 5
Gap:
Finding: Tests that create temp files don't have centralized cleanup.
Current State:
test/test_helper.rb includes cleanup for engine tables but not temp filestest/support/host_app_harness.rb manages temp directories for integration testsGap:
test/tmp/test/dummy/tmp/ grows unboundedCentralized in test_helper.rb:
def create_source!(attributes = {})
defaults = {
name: "Test Source",
feed_url: "https://example.com/feed-#{SecureRandom.hex(4)}.xml",
website_url: "https://example.com",
fetch_interval_minutes: 60,
scraper_adapter: "readability"
}
source = SourceMonitor::Source.new(defaults.merge(attributes))
source.save!(validate: false)
source
end
Local Patterns (per-file):
build_source(attributes) - wraps create_source! with defaultscreate_item(source:, guid:, title:, published_at:) - creates Item recordsModel.create! calls for other models (FetchLog, ScrapeLog, etc.)Minitest Stub (.stub):
Object.stub(:method, return_value) { code }
Anonymous Class Mocking:
fake = Class.new { def method; ...; end }.new
Direct Method Stubbing:
Class.stub(:method, value) { code }
stub_request(:get, "https://example.com/feed.xml")
.to_return(status: 200, body: File.read(file_fixture("feeds/rss_sample.xml")))
Standard Setup (test_helper.rb:82-91):
setup do
SourceMonitor.reset_configuration!
SourceMonitor.config.http.retry_max = 0
end
System Test Setup (per-file):
def setup
super
SourceMonitor.reset_configuration!
SourceMonitor::Jobs::Visibility.reset!
SourceMonitor::Jobs::Visibility.setup!
purge_solid_queue_tables
# dashboard-specific setup
end
Thread-Based Parallelism:
parallelize(workers: worker_count, with: :threads)
Scoped Assertions:
assert_equal 1, SourceMonitor::Item.where(source: source).count # GOOD
assert_equal 1, SourceMonitor::Item.count # BAD - counts across parallel tests
Create ApplicationSystemTestCase base class (T16)
test/dummy/app/test/application_system_test_case.rbpurge_solid_queue_tables, seed_queue_activity, apply_turbo_stream_messages, assert_item_orderCentralize Factory Helpers (T6)
test/test_helper_factories.rb or modulecreate_source!, build_source, create_item, create_fetch_log, create_scrape_logDocument VCR Strategy (T1)
test/VCR_README.mdsource_monitor/<domain>/<scenario> (existing partial convention)Add Unit Tests for Sub-Modules (T3)
test/lib/source_monitor/fetching/feed_fetcher/adaptive_interval_test.rb
test/lib/source_monitor/fetching/feed_fetcher/source_updater_test.rb
Add Integration Tests for Pipelines (T4)
test/integration/fetch_pipeline_integration_test.rb
test/integration/scrape_pipeline_integration_test.rb
Add Error Path Tests for Controllers (T10)
Add Error Scenario System Tests (T5)
Standardize Mocking Approach (T7)
stub (most concise)stubRemove Fixture Loading (T2)
fixtures :all with explicit per-test setup_fixtures for tests that need themAdd Concern Test Modules (T15)
test/support/shared_loggable_tests.rbStandardize Test Naming (T14)
Enforce Time Travel Cleanup (T11)
travel_to_at(time) { code } helper in test_helpertravel_back in ensureAdd Counter Cache Atomicity Tests (T13)
reset_items_counter! correctnessCentralize Job Testing Convention (T12)
:test vs :inline adapterwith_inline_jobs to test_helper (out of test_prof.rb)with_test_adapter helper for explicit test-mode testsAdd Temp File Cleanup Hook (T17)
test/tmp/* in teardown| Task | Effort |
|---|---|
| ApplicationSystemTestCase + helpers (T16, T9) | 2-3 hours |
| Centralize factories (T6) | 2-3 hours |
| VCR documentation (T1) | 1 hour |
| Sub-module unit tests (T3) | 3-4 hours |
| Integration tests (T4) | 4-5 hours |
| Controller error tests (T10) | 3-4 hours |
| System test error paths (T5) | 3-4 hours |
| Mocking standardization (T7) | 1-2 hours |
| Remaining consolidations (T2, T11-T15, T17) | 5-6 hours |
| Total | 25-32 hours |
Recommended Phase 06 Scope:
Future Phases:
| Metric | Current | Target |
|---|---|---|
| Test files | ~80 | ~100 (20 new) |
| Total tests | 1214 | 1350+ (136+ new) |
| Unit test coverage (sub-modules) | Partial | Complete |
| Integration test coverage | 4 scenarios | 10+ scenarios |
| System test error paths | 0% | 50%+ |
| Controller error tests | 1/controller | 5+/controller |
| Centralized factories | 1 (create_source!) | 6+ |
| ApplicationSystemTestCase | Missing | Present |
| VCR strategy doc | Missing | Present |