Files
sure/test/support/verify_api_endpoint_consistency.rb
T
Juan José Mata 157daf5176 Consolidate repository instructions after auditing their history (#3409)
* Document instruction inventory and preservation decisions

Trace main history from September 2025 through September 2026, including earlier policy origins. Record preserved requirements, detailed-guide destinations, stale facts, harness boundaries and explicit policy-strength decisions before consolidating instruction sources.

* Consolidate repository instructions into shared guidance

Keep AGENTS concise and vendor neutral, move detailed conventions into shared guides, and use thin adapters with preserved Cursor scopes. Preserve the strict pre-PR checks globally and document the stronger scope, retired migration pin and rule-generation trigger. Update existing API guidance verification without changing application behavior.

* Narrow the always-on Cursor UI adapter and correct the SimpleFIN comment

Split the design-system guidance out of docs/llm-guides/ui.md into
docs/llm-guides/design-system.md. The ui-ux-design-guidelines rule is
alwaysApply: true, so importing all of ui.md loaded the Stimulus,
localization and ViewComponent guidance (previously confined to scoped
rules) on every Cursor session; the always-on adapter now imports only the
design-system guide, matching the scope it had before the consolidation.
view_conventions and stimulus_conventions keep the full UI guide.

Also correct the stale Provider::Simplefin header comment: pending
inclusion defaults on and is resolved by the importer (explicit argument,
then SIMPLEFIN_INCLUDE_PENDING, then Setting.syncs_include_pending); the
previous comment described the flag as default-off.

* Read guidance files as UTF-8 in the API consistency validators

The frontmatter regex match ran against content read with the locale
default external encoding; the Cursor rule's description contains an em
dash, so under US-ASCII (LC_ALL=C) Regexp#match raised ArgumentError,
breaking the standalone no-Rails fallback the docs point contributors to.
Read all checked files with an explicit UTF-8 encoding in both the
standalone script and the Rails test.
2026-09-06 07:14:43 +02:00

129 lines
5.9 KiB
Ruby

# frozen_string_literal: true
# Standalone verification of the API endpoint consistency implementation (issue #944).
# Run without loading Rails: ruby test/support/verify_api_endpoint_consistency.rb
# Or with bundle: bundle exec ruby test/support/verify_api_endpoint_consistency.rb
#
# Option: pass --compliance to also scan the current API codebase and report violations
# (rswag specs using OAuth instead of API key, missing Minitest for API controllers,
# rswag specs with expect/assert).
require "yaml"
def project_root
dir = File.dirname(File.expand_path(__FILE__))
loop do
return dir if File.exist?(File.join(dir, "AGENTS.md")) && File.directory?(File.join(dir, "docs", "llm-guides"))
parent = File.dirname(dir)
raise "Could not find project root (AGENTS.md + docs/llm-guides)" if parent == dir
dir = parent
end
end
def assert(condition, message)
raise "FAIL: #{message}" unless condition
end
def assert_includes(content, substring, message)
assert content.include?(substring), "#{message} (missing: #{substring.inspect})"
end
root = project_root
rule_path = File.join(root, ".cursor", "rules", "api-endpoint-consistency.mdc")
guide_path = File.join(root, "docs", "llm-guides", "api-endpoint-consistency.md")
agents_path = File.join(root, "AGENTS.md")
assert File.exist?(rule_path), "Rule file should exist at #{rule_path}"
rule_content = File.read(rule_path, encoding: "UTF-8")
frontmatter = rule_content.match(/\A---\r?\n(?<yaml>.*?)\r?\n---\r?\n(?<body>.*)\z/m)
assert frontmatter, "Rule must start with YAML frontmatter"
keys = YAML.parse(frontmatter[:yaml]).root.children.each_slice(2).map { |key, _value| key.value }
assert keys.sort == %w[alwaysApply description globs], "Rule must have exactly one description, globs and alwaysApply key"
metadata = YAML.safe_load(frontmatter[:yaml])
expected_globs = [
"app/controllers/api/v1/**/*.rb",
"spec/requests/api/v1/**/*.rb",
"test/controllers/api/v1/**/*.rb"
]
assert metadata.fetch("globs").split(",").map(&:strip) == expected_globs, "Rule must retain exactly the API v1 globs"
assert metadata.fetch("alwaysApply") == false, "Rule must remain scoped, not always applied"
assert frontmatter[:body].strip == "@docs/llm-guides/api-endpoint-consistency.md", "Rule must import shared checklist"
assert File.exist?(guide_path), "Shared checklist should exist at #{guide_path}"
guide_content = File.read(guide_path, encoding: "UTF-8")
assert_includes guide_content, "Minitest behavioral coverage", "Shared checklist must include Minitest section"
assert_includes guide_content, "test/controllers/api/v1/{resource}_controller_test.rb", "Shared checklist must specify Minitest location"
assert_includes guide_content, "api_headers", "Shared checklist must mention api_headers"
assert_includes guide_content, "X-Api-Key", "Shared checklist must mention X-Api-Key"
assert_includes guide_content, "rswag is docs-only", "Shared checklist must include rswag docs-only section"
assert_includes guide_content, "run_test!", "Shared checklist must mention run_test!"
assert_includes guide_content, "rswag:specs:swaggerize", "Shared checklist must mention swaggerize task"
assert_includes guide_content, "Same API key auth", "Shared checklist must include API key auth section"
assert_includes guide_content, "ApiKey.generate_secure_key", "Shared checklist must show API key pattern"
assert_includes guide_content, "plain_key", "Shared checklist must mention plain_key"
assert_includes guide_content, "Doorkeeper", "Shared checklist must mention Doorkeeper (to avoid OAuth in specs)"
assert File.exist?(agents_path), "AGENTS.md should exist"
agents_content = File.read(agents_path, encoding: "UTF-8")
assert_includes agents_content, "Post-commit API consistency", "AGENTS.md must reference post-commit checklist"
assert_includes agents_content, "docs/llm-guides/api-endpoint-consistency.md", "AGENTS.md must link to shared checklist"
assert_includes agents_content, "Minitest", "AGENTS.md must mention Minitest"
assert_includes agents_content, "rswag", "AGENTS.md must mention rswag"
assert_includes agents_content, "X-Api-Key", "AGENTS.md must mention X-Api-Key"
puts "OK: API endpoint consistency implementation verified (shared checklist + scoped adapter + AGENTS.md)."
if ARGV.include?("--compliance")
puts "\n--- Compliance check (current APIs) ---"
spec_dir = File.join(root, "spec", "requests", "api", "v1")
test_dir = File.join(root, "test", "controllers", "api", "v1")
app_controllers_dir = File.join(root, "app", "controllers", "api", "v1")
rswag_oauth = []
rswag_assertions = []
missing_minitest = []
if File.directory?(spec_dir)
Dir.glob(File.join(spec_dir, "*_spec.rb")).each do |path|
basename = File.basename(path, "_spec.rb")
next if basename == "auth"
content = File.read(path, encoding: "UTF-8")
if content.include?("Doorkeeper") || content.include?("Bearer") || content.include?("access_token")
rswag_oauth << "#{basename}_spec.rb"
end
rswag_assertions << "#{basename}_spec.rb" if content.include?("expect(") || content.include?("assert_")
end
end
skip_controllers = %w[base_controller test_controller]
if File.directory?(app_controllers_dir)
Dir.glob(File.join(app_controllers_dir, "*_controller.rb")).each do |path|
basename = File.basename(path, ".rb")
next if skip_controllers.include?(basename)
test_path = File.join(test_dir, "#{basename}_test.rb")
missing_minitest << basename unless File.exist?(test_path)
end
end
if rswag_oauth.any?
puts "rswag using OAuth (should use API key per rule): #{rswag_oauth.join(", ")}"
else
puts "rswag auth: all specs use API key."
end
if rswag_assertions.any?
puts "rswag with expect/assert (should be docs-only): #{rswag_assertions.join(", ")}"
else
puts "rswag: no expect/assert found (docs-only)."
end
if missing_minitest.any?
puts "API v1 controllers missing Minitest: #{missing_minitest.join(", ")}"
else
puts "Minitest: all API v1 controllers have a test file."
end
puts "---"
end