Repository navigation
Add an end-to-end test suite against a real ActiveAdmin install - #9
Merged
Merged
Conversation
Generates a real Rails + ActiveAdmin application with the gem installed into it, cached at tmp/e2e_app between runs, driven by a new 'rake e2e' task kept out of the default suite. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
RSpec's --require option is additive, so the e2e task's own --require was merging with the project .rspec's --require spec_helper rather than replacing it, silently loading the unit suite's in-memory SQLite setup and after-hook during every e2e run. Switch to --options spec/e2e/e2e.opts, which replaces the project .rspec wholesale instead of merging with it, and drop the now-unneeded --exclude-pattern override. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds a Puma harness, a Net::HTTP JSON-RPC client, and specs covering the initialize handshake, the advertised tool list, and Bearer token authentication against a live server. Also pins the json gem below 2.9 in the generated app's Gemfile: Rails 7.2's ActiveSupport::JSON.decode still calls JSON.parse with the quirks_mode keyword, which json >= 2.9.0 removed, so every request with a JSON body 500'd until this was pinned back. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Exercises list_resources, query and update against the live server, including that unpermitted attributes are dropped and that a resource without an update action is refused. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds an e2e job alongside rspec and mcpb, caching the generated host application on the builder's hash and uploading the Rails log when the suite fails. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…g integrity - Vendor the generated app's bundle inside tmp/e2e_app and run bundle install/seed on every build (including the cached path), so a CI cache hit restores a runnable app instead of one missing its gems. - Make seed data restorative (find_or_initialize_by + save!) and run it on every invocation, so mutations from the update examples don't leak between runs via the local or CI cache. - Stop stdout/stderr from clobbering each other in the Puma log, since the log tail is the only diagnostic for a boot failure. - Validate wait_for_boot!'s response (401, or 200 JSON) instead of accepting any HTTP answer, and widen its rescued exceptions. - Require "bundler" explicitly; drop two assertions in app_install_spec.rb that couldn't fail independently of the builder; assert records.length alongside count in the limit example; document the Ransack allowlist requirement and the e2e suite itself in the README. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds a CLAUDE.md recording that every MCP action needs an e2e test, not just unit specs: nearly everything this gem does is a claim about code it does not own (ActiveAdmin's permitted params, the host's authorization adapter, Ransack, the host route set), and a mock cannot show those actually behave as expected when wired together. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Bumps all three jobs and the release workflow from 3.4 to the latest Ruby 4 release. The gemspec's required_ruby_version is deliberately left at ">= 3.0.0": that is the floor for consumers of the gem, and raising it would drop every host application still on Ruby 3. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Raises required_ruby_version from ">= 3.0.0" to ">= 4.0.0", with the README's Requirements list and a breaking-change CHANGELOG entry to match. This drops support for host applications on Ruby 3.x. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The ActiveAdmin registrations, model allowlists, migrations, seeds and Gemfile additions the e2e suite tests against were written from heredocs inside the builder, where nobody could read them. They are now checked in under spec/e2e/fixture_app/ and copied over the generated application, so the example app can be inspected and edited directly. The migrations in particular were previously invisible: they came from `rails generate model` with build-time timestamps. Checking them in with fixed versions makes the schema under test both visible and stable, and drops two generator shell-outs from the build. The cache fingerprint now covers the fixture tree as well as the builder. Without that, moving the fixtures out of the builder's own digest would have meant an edited fixture was silently ignored on the next run — the exact failure this suite exists to catch. Also snapshots the SQLite database after migrating and seeding, and restores it by file copy on cached runs rather than booting Rails to re-seed. Cached runs go from 4.2s to 3.4s. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Requiring Ruby >= 4.0 left the declared support matrix unsatisfiable: Rails 6.1 and ActiveAdmin 2.0 cannot run on it, so the stated requirements described a combination nobody could install. Rails 7.2 is the oldest release this gem's own end-to-end suite runs on Ruby 4, and ActiveAdmin 2.x does not support Rails 7.2, so the floors follow from the Ruby one. Updated in the gemspec as well as the README, since both declared them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Replaces the open ">= 3.0" dependency with "~> 3.5", so the gem declares the series it is actually tested against and ActiveAdmin 4 has to be adopted deliberately rather than arriving through a host application's bundle update. The e2e fixture application's Gemfile moves from "~> 3.2" to match, so the suite installs what the gemspec supports rather than something merely compatible with it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Removes the comment from all 41 Ruby files, including the generator templates that write it into host applications. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Examples previously shared one database for the whole run, so the two update examples had to mutate the same post in a fixed sequence and the suite was pinned to :defined order. Any new example that asserted a seeded value would have passed on a fresh build and failed on every rerun. Restoring the seeded rows now happens in a before hook. It is a transaction against the live database rather than a Rails boot, so the cost is milliseconds and the suite can afford to pay it 19 times: cached runs are 2.8s, marginally faster than the 3.4s they took while resetting once per run. The restore goes through SQLite rather than copying the snapshot file over the database, because the application server holds that file open and swapping it underneath its page cache invites inconsistent reads. The API token table is excluded: the token is minted after the snapshot is taken, so restoring it would revoke the token and every subsequent request would come back 401. With no example depending on another, order is now :random, which is what keeps the isolation honest. Also records in CLAUDE.md that e2e descriptions should be verbose enough that the documentation-format output reads as a specification of the MCP interface on its own. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Every spec in this repo was a unit test against mocks — nothing booted Rails, mounted the engine, or served an HTTP request. So the README's claims (the installer wires it up, Bearer tokens authenticate,
permit_paramsis enforced, resources without anupdateaction are refused) were verified only against mocks of the surrounding framework.This adds
spec/e2e/, which generates a real Rails 7.2 + ActiveAdmin + Devise application intotmp/e2e_app, installs this gem into it through the gem's ownactiveadmin_mcp:install --auth devise_tokengenerator, boots it under Puma, and drives the mounted/mcpendpoint over HTTP with a real API token.No browser is involved — the MCP endpoint is plain JSON-RPC over HTTP, so one would add nothing.
What it covers
19 examples:
authentication_method = :devise_token, the token migration and MCP Tokens admin page generated, the fixture tables migratedinitializereturns protocol2025-06-18and the rightserverInfo;tools/listadvertises exactly the three tools-32000; the minted token gets 200list_resources— resources and attributes, with Devise's sensitive columns filtered out of a realAdminUsertablequery— Ransack filtering,limit, unknown resource returns an error rather than raisingupdate— a permitted attribute persists; an unpermitted one (slug) is dropped; a resource registeredactions :index, :showis refused; a missing record returns an errorThe two fixture resources are chosen so each claim has something to bite on:
Postpermits onlytitleandbody, andAuthorregisters noupdateaction.Why this is worth the machinery
The auth examples are the only coverage
McpControllerhas anywhere in the repo. The permit-drop example reachesRecordUpdater#from_permit_params, which instantiates the real ActiveAdmin controller and reads its compiled permitted params — a path that cannot be honestly unit-tested. And getting a 200 fromPOST /mcpat all proves the engine's:prependmount strategy works inside a real ActiveAdmin route set.The suite was checked for vacuity by hand: permitting
slugin the generated app makes the relevant example fail withexpected: ["title"], got: ["title", "slug"].Running it
The generated app is cached by a fingerprint of the builder, so only the first run is slow (a full
bundle install, and agem install rails -v 7.2.2.2if that version is absent).E2E_REBUILD=1forces a rebuild. Documented in the README's new "Running the tests" section.A CI job runs it on every PR, caching
tmp/e2e_appand uploading the Rails log on failure.Known issue this surfaced, deliberately not fixed here
RequestHandler#tool_querycallsmodel.ransack(q)unguarded, andMcpControllerrescues onlyJSON::ParserError. On Ransack 4, a host model that has not allowlistedransackable_attributestherefore raises straight through the controller and the MCP client receives an HTML 500 instead of a JSON-RPC error. That is most applications that have not done the ActiveAdmin 3 upgrade chore.The e2e builder works around it by writing
ransackable_attributesonto the fixture models, and the README now documents the requirement — but the library should degrade gracefully instead. That is a change tolib/behaviour and belongs in its own PR.Notes for review
gem "json", "< 2.9"is pinned in the generated app's Gemfile: Rails 7.2'sActiveSupport::JSON.decodecallsJSON.parse(json, quirks_mode: true)and the json gem removed that keyword in 2.9.0.CLAUDE.mdis new, recording that MCP actions need e2e coverage rather than unit specs alone.🤖 Generated with Claude Code