admin_suite 0.6.1 → 0.6.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 964267328a54fb0d55bfb035fab32ba694b620b0f5cbdb45194e770a3138885f
4
- data.tar.gz: eec49bd217460a272eb6ce1add0fd9ac487764a37e304bdcde105044f7eed80a
3
+ metadata.gz: dfafbff75d098582ef9df840c364f47ca801dc8df3d01b7be84f009b04f73f73
4
+ data.tar.gz: e91b0ffd0d579b5b07e3ff4ecdb5ad3bb829b761d86946aeface7148d6646a21
5
5
  SHA512:
6
- metadata.gz: 5875a1f28a12afb57b1e43c38db7f4362f6ef896f2b0de7f0af2d949945ac1c60e17f0e24a5b3f967f753fdeac8960470f2076421ef31156f3fb64b03285fbd2
7
- data.tar.gz: 76e74fce525a385fad34c84511f2affbfeca6df6ea147e81dfdaa393051c3b98915055f1b620040a140c6ce5e336823fc66928d2a0b01ca9017881914d530185
6
+ metadata.gz: 17b32e4625cc21f3a64fbecf03593c96a731bdec745b6e0accaf1464fbe8bbbbf0728c3efc135396ec57055f888fe04ba30463ff61c85bf8741766861a5ce327
7
+ data.tar.gz: 379e5d5ce01d0d22d7b3b9be52f2ebbcc409ec4f04a3ea0d6403b238540f1eb4749c9b172c115b9832d3baf90ce686b46eddeaa551a670637c72ee99d12bbc41
data/CHANGELOG.md CHANGED
@@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.6.2] - 2026-10-03
9
+
10
+ ### Fixed
11
+ - Restored database-free dummy application boot and executable action coverage.
12
+ - Updated the development dummy application to Ruby 3.4.10.
13
+
14
+ ### Changed
15
+ - Added architecture boundary checks and baseline validation to local and hosted CI.
16
+
8
17
  ## [0.6.1] - 2026-09-12
9
18
 
10
19
  ### Fixed
data/Gemfile CHANGED
@@ -6,6 +6,7 @@ gemspec
6
6
  group :development, :test do
7
7
  # Provides the asset pipeline used by the engine/dummy app.
8
8
  gem "propshaft"
9
+ gem "archspec", "1.1.0", require: false
9
10
  end
10
11
 
11
12
  group :test do
data/README.md CHANGED
@@ -20,6 +20,10 @@ Canonical AdminSuite documentation lives in the TechWright vault at
20
20
  `../_vault/products/admin_suite/docs/`. This repo intentionally has no root
21
21
  `docs/` tree or docs symlink.
22
22
 
23
+ ## Architecture check
24
+
25
+ `bin/archspec check` and `bin/archspec-baseline` enforce engine, namespace, and dummy-app boundaries in `Archspec.rb`. They do not boot Rails. Do not grow `archspec_todo.yml`.
26
+
23
27
  ## Quickstart
24
28
 
25
29
  Add the gem:
@@ -2,7 +2,7 @@
2
2
 
3
3
  module AdminSuite
4
4
  module Version
5
- VERSION = "0.6.1"
5
+ VERSION = "0.6.2"
6
6
  end
7
7
 
8
8
  # Backward-compatible constant.
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "minitest/autorun" unless defined?(Rails)
4
+ require "yaml"
5
+ require "open3"
6
+ require "tmpdir"
7
+ require "fileutils"
8
+
9
+ class ArchspecEnforcementTest < Minitest::Test
10
+ ROOT = File.expand_path("../..", __dir__)
11
+
12
+ def test_pull_request_architecture_job_is_fail_closed_and_matches_bin_ci
13
+ workflow = YAML.load_file(File.join(ROOT, ".github/workflows/ci.yml"))
14
+ job = workflow.fetch("jobs").fetch("architecture")
15
+ assert_equal "ubuntu-latest", job["runs-on"]
16
+ assert_nil job["continue-on-error"]
17
+ assert_nil job["if"]
18
+
19
+ commands = job.fetch("steps").filter_map { |step| step["run"] }
20
+ assert_includes commands, "bin/archspec check"
21
+ assert_includes commands, "bin/archspec-baseline"
22
+ assert commands.none? { |command| command.include?("continue-on-error") || command.include?("[ -f") }
23
+
24
+ ci = File.read(File.join(ROOT, "bin/ci"))
25
+ assert_includes ci, 'step "Architecture: ArchSpec" bin/archspec check'
26
+ assert_includes ci, 'step "Architecture: baseline cap" bin/archspec-baseline'
27
+ refute_match(/if \[ -f bin\/archspec \]/, ci)
28
+ refute_match(/if \[ -f Archspec\.rb \]/, ci)
29
+ assert_equal "read", job.dig("permissions", "contents")
30
+ end
31
+
32
+ def test_mcp_cannot_call_rejects_a_write
33
+ spec = File.read(File.join(ROOT, "Archspec.rb"))
34
+ rule = spec[/mcp\.cannot_call\b.*?receiver: :any/m]
35
+ assert rule, "mcp.cannot_call must forbid writes on any receiver"
36
+ refute_match(/ui\.cannot_call/, rule)
37
+ assert_match(/queries\.cannot_use\b[^\n]*:mcp\b/, spec)
38
+
39
+ Dir.mktmpdir do |dir|
40
+ FileUtils.mkdir_p(File.join(dir, "lib/admin_suite/mcp"))
41
+ File.write(File.join(dir, "Archspec.rb"), spec)
42
+ File.write(File.join(dir, "archspec_todo.yml"), File.read(File.join(ROOT, "archspec_todo.yml")))
43
+ File.write(File.join(dir, "lib/admin_suite/mcp/probe.rb"), <<~RUBY)
44
+ module AdminSuite
45
+ module Mcp
46
+ class Probe
47
+ def call(record)
48
+ record.save
49
+ end
50
+ end
51
+ end
52
+ end
53
+ RUBY
54
+
55
+ output, status = run_archspec(dir, "check")
56
+ refute status.success?, output
57
+ assert_match(/must not call #save/, output)
58
+ end
59
+ end
60
+
61
+ def test_missing_and_invalid_configuration_fail
62
+ Dir.mktmpdir do |dir|
63
+ _output, status = run_archspec(dir, "check", "--config", File.join(dir, "missing.rb"))
64
+ refute status.success?
65
+ end
66
+
67
+ Dir.mktmpdir do |dir|
68
+ File.write(File.join(dir, "Archspec.rb"), "this is not ruby (\n")
69
+ _output, status = run_archspec(dir, "check")
70
+ refute status.success?
71
+ end
72
+ end
73
+
74
+ def test_stale_baseline_id_fails_and_current_set_passes
75
+ Dir.mktmpdir do |dir|
76
+ FileUtils.mkdir_p(File.join(dir, "app/models"))
77
+ File.write(File.join(dir, "app/models/widget.rb"), "class Widget\nend\n")
78
+ File.write(File.join(dir, "Archspec.rb"), <<~CONFIG)
79
+ todo "archspec_todo.yml"
80
+ component :models, in: "app/models/**/*.rb"
81
+ models.cannot_reference_constants "Nope"
82
+ CONFIG
83
+ File.write(File.join(dir, "archspec_todo.yml"), <<~YAML)
84
+ ---
85
+ violations: []
86
+ YAML
87
+
88
+ _output, status = run_baseline(dir)
89
+ assert status.success?, "empty matching baseline must pass"
90
+
91
+ File.write(File.join(dir, "archspec_todo.yml"), <<~YAML)
92
+ ---
93
+ violations:
94
+ - id: deadbeefdeadbeefdeadbeef
95
+ rule: constants.forbid
96
+ path: app/models/widget.rb
97
+ line: 1
98
+ message: gone
99
+ YAML
100
+ output, status = run_baseline(dir)
101
+ refute status.success?
102
+ assert_match(/stale deadbeefdeadbeefdeadbeef/, output)
103
+ end
104
+ end
105
+
106
+ private
107
+
108
+ def run_archspec(dir, *args)
109
+ Open3.capture2e({ "BUNDLE_GEMFILE" => File.join(ROOT, "Gemfile") }, File.join(ROOT, "bin/archspec"), *args, chdir: dir)
110
+ end
111
+
112
+ def run_baseline(dir)
113
+ Open3.capture2e({ "BUNDLE_GEMFILE" => File.join(ROOT, "Gemfile") }, File.join(ROOT, "bin/archspec-baseline"), chdir: dir)
114
+ end
115
+ end
@@ -31,14 +31,17 @@ Rails.application.configure do
31
31
  # Print deprecation notices to the Rails logger.
32
32
  config.active_support.deprecation = :log
33
33
 
34
- # Raise an error on page load if there are pending migrations.
35
- config.active_record.migration_error = :page_load
34
+ # The dummy app can run without the Active Record railtie.
35
+ if config.respond_to?(:active_record)
36
+ # Raise an error on page load if there are pending migrations.
37
+ config.active_record.migration_error = :page_load
36
38
 
37
- # Highlight code that triggered database queries in logs.
38
- config.active_record.verbose_query_logs = true
39
+ # Highlight code that triggered database queries in logs.
40
+ config.active_record.verbose_query_logs = true
39
41
 
40
- # Append comments with runtime information tags to SQL queries in logs.
41
- config.active_record.query_log_tags_enabled = true
42
+ # Append comments with runtime information tags to SQL queries in logs.
43
+ config.active_record.query_log_tags_enabled = true
44
+ end
42
45
 
43
46
  # Highlight code that triggered redirect in logs.
44
47
  config.action_dispatch.verbose_redirect_logs = true
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+ require "open3"
5
+ require "rbconfig"
6
+
7
+ class DummyBootTest < ActiveSupport::TestCase
8
+ test "database-free dummy boots and eager loads in development" do
9
+ script = <<~RUBY
10
+ require_relative "test/dummy/config/environment"
11
+ abort "Unexpected ActiveRecord railtie" if Rails.application.config.respond_to?(:active_record)
12
+ Rails.application.eager_load!
13
+ RUBY
14
+ stdout, stderr, status = Open3.capture3(
15
+ { "RAILS_ENV" => "development" }, RbConfig.ruby, "-e", script,
16
+ chdir: File.expand_path("..", __dir__)
17
+ )
18
+
19
+ assert status.success?, "Development boot failed:\n#{stdout}#{stderr}"
20
+ end
21
+ end
@@ -2,6 +2,8 @@
2
2
 
3
3
  require "test_helper"
4
4
  require "tmpdir"
5
+ require "open3"
6
+ require "rbconfig"
5
7
 
6
8
  module Admin
7
9
  module Base
@@ -96,16 +98,22 @@ module Admin
96
98
  end
97
99
 
98
100
  test "load_action_handlers_for_admin_suite returns early when AdminSuite is not defined" do
99
- # Temporarily undefine AdminSuite to test early return
100
- admin_suite_defined = defined?(AdminSuite)
101
-
102
- skip "Cannot test AdminSuite undefined condition when AdminSuite is required" if admin_suite_defined
103
-
104
- resource_class = Struct.new(:resource_name).new("test")
105
- executor = ActionExecutor.new(resource_class, :test, nil)
101
+ # Bundler evaluates the gemspec and defines AdminSuite::VERSION.
102
+ # Bypass its RUBYOPT preload in this dependency-free subprocess.
103
+ script = <<~RUBY
104
+ require_relative "lib/admin/base/action_executor"
105
+ abort "AdminSuite unexpectedly loaded" if defined?(AdminSuite)
106
+ executor = Admin::Base::ActionExecutor.new(nil, :test, nil)
107
+ result = executor.send(:load_action_handlers_for_admin_suite!)
108
+ abort "Expected early return" unless result.nil?
109
+ abort "Handlers marked loaded" if Admin::Base::ActionExecutor.handlers_loaded
110
+ RUBY
111
+ stdout, stderr, status = Open3.capture3(
112
+ { "RUBYOPT" => nil }, RbConfig.ruby, "--disable-gems", "-e", script,
113
+ chdir: File.expand_path("../..", __dir__)
114
+ )
106
115
 
107
- # This should return early without error
108
- assert_nil executor.send(:load_action_handlers_for_admin_suite!)
116
+ assert status.success?, "Isolated executor failed:\n#{stdout}#{stderr}"
109
117
  end
110
118
 
111
119
  test "load_action_handlers_for_admin_suite handles empty action_globs gracefully" do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: admin_suite
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - TechWright Labs
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-09-12 00:00:00.000000000 Z
11
+ date: 2026-10-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -262,6 +262,7 @@ files:
262
262
  - lib/generators/admin_suite/scaffold/scaffold_generator.rb
263
263
  - lib/tasks/admin_suite_tailwind.rake
264
264
  - lib/tasks/admin_suite_test.rake
265
+ - test/archspec/enforcement_test.rb
265
266
  - test/controllers/resources_controller_test.rb
266
267
  - test/dummy/Gemfile
267
268
  - test/dummy/README.md
@@ -305,6 +306,7 @@ files:
305
306
  - test/dummy/public/icon.svg
306
307
  - test/dummy/public/robots.txt
307
308
  - test/dummy/test/test_helper.rb
309
+ - test/dummy_boot_test.rb
308
310
  - test/fixtures/docs/progress/PROGRESS_REPORT.md
309
311
  - test/integration/association_linking_test.rb
310
312
  - test/integration/authentication_test.rb