admin_suite 0.4.0 → 0.6.1
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/.gitignore +7 -0
- data/CHANGELOG.md +203 -18
- data/CONTRIBUTING.md +9 -4
- data/README.md +21 -5
- data/app/assets/vendor/chart.umd.min.js +14 -0
- data/app/controllers/admin_suite/application_controller.rb +12 -11
- data/app/controllers/admin_suite/mcp_controller.rb +36 -0
- data/app/controllers/admin_suite/resources_controller.rb +71 -17
- data/app/helpers/admin_suite/base_helper.rb +187 -10
- data/app/javascript/admin_suite_application.js +3 -0
- data/app/javascript/controllers/admin_suite/chart_controller.js +173 -0
- data/app/views/admin_suite/panels/_chart.html.erb +158 -18
- data/app/views/admin_suite/resources/index.html.erb +25 -5
- data/app/views/admin_suite/shared/_sidebar.html.erb +14 -8
- data/app/views/layouts/admin_suite/application.html.erb +6 -0
- data/config/routes.rb +8 -0
- data/lib/admin/base/filter_builder.rb +48 -5
- data/lib/admin/base/resource.rb +46 -32
- data/lib/admin_suite/auth/host_user.rb +42 -0
- data/lib/admin_suite/auth/strategy.rb +1 -1
- data/lib/admin_suite/auth.rb +15 -0
- data/lib/admin_suite/authorization_context.rb +28 -0
- data/lib/admin_suite/configuration.rb +66 -8
- data/lib/admin_suite/engine.rb +1 -1
- data/lib/admin_suite/legacy_custom_renderer_procs.rb +1 -1
- data/lib/admin_suite/mcp/authorization.rb +68 -0
- data/lib/admin_suite/mcp/serializer.rb +138 -0
- data/lib/admin_suite/mcp/tools/aggregate.rb +55 -0
- data/lib/admin_suite/mcp/tools/describe_resources.rb +63 -0
- data/lib/admin_suite/mcp/tools/get_record.rb +41 -0
- data/lib/admin_suite/mcp/tools/list_records.rb +67 -0
- data/lib/admin_suite/mcp.rb +53 -0
- data/lib/admin_suite/query.rb +71 -0
- data/lib/admin_suite/renderer_registry.rb +11 -0
- data/lib/admin_suite/ui/dashboard_definition.rb +6 -0
- data/lib/admin_suite/ui/show_value_formatter.rb +2 -2
- data/lib/admin_suite/version.rb +1 -1
- data/lib/admin_suite.rb +23 -7
- data/lib/generators/admin_suite/install/templates/admin_suite.rb +5 -2
- data/test/controllers/resources_controller_test.rb +19 -13
- data/test/integration/association_linking_test.rb +292 -0
- data/test/integration/authentication_test.rb +10 -0
- data/test/integration/authorization_test.rb +20 -3
- data/test/integration/chart_panel_test.rb +491 -0
- data/test/integration/dashboard_test.rb +9 -2
- data/test/integration/index_query_characterization_test.rb +229 -0
- data/test/integration/index_table_test.rb +289 -0
- data/test/integration/mcp_aggregate_test.rb +44 -0
- data/test/integration/mcp_authorization_test.rb +102 -0
- data/test/integration/mcp_endpoint_test.rb +89 -0
- data/test/integration/mcp_get_record_test.rb +62 -0
- data/test/integration/mcp_instrumentation_test.rb +107 -0
- data/test/integration/mcp_list_records_test.rb +75 -0
- data/test/integration/mcp_parity_test.rb +155 -0
- data/test/integration/mcp_release_test.rb +131 -0
- data/test/integration/navigation_sections_test.rb +21 -0
- data/test/integration/read_only_resource_test.rb +128 -2
- data/test/integration/searchable_select_search_test.rb +369 -0
- data/test/integration/show_hide_blank_test.rb +195 -0
- data/test/integration/toggle_test.rb +106 -0
- data/test/lib/auth_host_user_test.rb +52 -0
- data/test/lib/auth_http_basic_test.rb +10 -0
- data/test/lib/auth_test.rb +20 -3
- data/test/lib/authorization_context_test.rb +127 -0
- data/test/lib/definition_loader_test.rb +12 -0
- data/test/lib/engine_defaults_test.rb +1 -2
- data/test/lib/form_field_renderer_test.rb +83 -11
- data/test/lib/format_table_cell_test.rb +49 -0
- data/test/lib/index_includes_test.rb +223 -0
- data/test/lib/mcp_serializer_test.rb +107 -0
- data/test/lib/query_test.rb +91 -0
- data/test/lib/removed_deprecations_test.rb +16 -0
- data/test/lib/renderer_test.rb +23 -7
- data/test/publish_workflow_test.rb +63 -0
- data/test/test_helper.rb +73 -8
- metadata +76 -9
- data/lib/admin_suite/renderers/legacy_gleania.rb +0 -230
- data/test/lib/legacy_renderer_deprecation_test.rb +0 -35
- data/test/lib/resource_exportable_deprecation_test.rb +0 -39
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "minitest/autorun"
|
|
4
|
+
require "yaml"
|
|
5
|
+
|
|
6
|
+
class PublishWorkflowTest < Minitest::Test
|
|
7
|
+
WORKFLOW_PATH = File.expand_path("../.github/workflows/publish.yml", __dir__)
|
|
8
|
+
|
|
9
|
+
def setup
|
|
10
|
+
@source = File.read(WORKFLOW_PATH)
|
|
11
|
+
@workflow = YAML.load_file(WORKFLOW_PATH)
|
|
12
|
+
@job = @workflow.fetch("jobs").fetch("publish")
|
|
13
|
+
@steps = @job.fetch("steps")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def test_publishing_uses_the_release_environment_and_job_scoped_oidc
|
|
17
|
+
assert_equal "release", @job["environment"]
|
|
18
|
+
assert_equal "write", @job.fetch("permissions")["id-token"]
|
|
19
|
+
assert_equal "write", @job.fetch("permissions")["contents"]
|
|
20
|
+
refute @workflow.fetch("permissions", {}).key?("id-token")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def test_automatic_publishing_requires_successful_main_push_ci
|
|
24
|
+
condition = @job.fetch("if")
|
|
25
|
+
assert_includes condition, "github.repository == 'techwright-lab/admin_suite'"
|
|
26
|
+
assert_includes condition, "github.event_name == 'workflow_run'"
|
|
27
|
+
assert_includes condition, "github.event.workflow_run.conclusion == 'success'"
|
|
28
|
+
assert_includes condition, "github.event.workflow_run.event == 'push'"
|
|
29
|
+
assert_includes condition, "github.event.workflow_run.head_branch == 'main'"
|
|
30
|
+
assert_includes condition, "github.event.workflow_run.head_repository.full_name == github.repository"
|
|
31
|
+
assert_includes condition, "github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main'"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def test_credentials_are_pinned_conditional_and_before_the_push
|
|
35
|
+
credentials_index = @steps.index { |step| step["name"] == "Configure RubyGems trusted publishing" }
|
|
36
|
+
refute_nil credentials_index
|
|
37
|
+
credentials = @steps.fetch(credentials_index)
|
|
38
|
+
push_index = @steps.index { |step| step["name"] == "Publish to RubyGems" }
|
|
39
|
+
push = @steps.fetch(push_index)
|
|
40
|
+
|
|
41
|
+
assert_equal "rubygems/configure-rubygems-credentials@dc5a8d8553e6ee01fc26761a49e99e733d17954a", credentials["uses"]
|
|
42
|
+
assert_equal "steps.version_check.outputs.should_publish == 'true'", credentials["if"]
|
|
43
|
+
assert_equal credentials["if"], push["if"]
|
|
44
|
+
assert_operator credentials_index, :<, push_index
|
|
45
|
+
refute_includes @source, "secrets.RUBYGEMS_API_KEY"
|
|
46
|
+
refute_includes @source, "GEM_HOST_API_KEY:"
|
|
47
|
+
refute_includes @source, "~/.gem/credentials"
|
|
48
|
+
assert_includes push.fetch("run"), "gem push"
|
|
49
|
+
assert_includes push.fetch("run"), "--host https://rubygems.org"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def test_releases_preserve_tested_sha_and_serialization
|
|
53
|
+
checkout = @steps.find { |step| step.fetch("uses", "").start_with?("actions/checkout@") }
|
|
54
|
+
assert_equal "${{ github.event.workflow_run.head_sha || github.sha }}", checkout.fetch("with")["ref"]
|
|
55
|
+
assert_equal false, @workflow.fetch("concurrency")["cancel-in-progress"]
|
|
56
|
+
assert_includes @source, "different contents. Bump the gem version before publishing."
|
|
57
|
+
push_index = @steps.index { |step| step["name"] == "Publish to RubyGems" }
|
|
58
|
+
tag_index = @steps.index { |step| step["name"] == "Create Git tag" }
|
|
59
|
+
release_index = @steps.index { |step| step["name"] == "Create GitHub Release" }
|
|
60
|
+
assert_operator push_index, :<, tag_index
|
|
61
|
+
assert_operator tag_index, :<, release_index
|
|
62
|
+
end
|
|
63
|
+
end
|
data/test/test_helper.rb
CHANGED
|
@@ -16,6 +16,15 @@ if ENV["COVERAGE"]
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
require_relative "dummy/config/environment"
|
|
19
|
+
|
|
20
|
+
# Auth strategies are process-global. Tests that register temporary strategies
|
|
21
|
+
# can restore a snapshot with this helper to avoid cross-file registry leakage.
|
|
22
|
+
def with_auth_registry_snapshot
|
|
23
|
+
registry = AdminSuite::Auth.instance_variable_get(:@registry).dup
|
|
24
|
+
yield
|
|
25
|
+
ensure
|
|
26
|
+
AdminSuite::Auth.instance_variable_set(:@registry, registry)
|
|
27
|
+
end
|
|
19
28
|
require "minitest/autorun"
|
|
20
29
|
require "active_support/test_case"
|
|
21
30
|
require "action_dispatch/testing/integration"
|
|
@@ -23,6 +32,14 @@ require "action_dispatch/testing/integration"
|
|
|
23
32
|
# Ensure the engine is loaded (and its initializers run).
|
|
24
33
|
require "admin_suite"
|
|
25
34
|
|
|
35
|
+
def with_authorize(hook)
|
|
36
|
+
previous = AdminSuite.config.authorize
|
|
37
|
+
AdminSuite.config.authorize = hook
|
|
38
|
+
yield
|
|
39
|
+
ensure
|
|
40
|
+
AdminSuite.config.authorize = previous
|
|
41
|
+
end
|
|
42
|
+
|
|
26
43
|
# The dummy app is intentionally database-free, while the generic controller
|
|
27
44
|
# supports Active Record hosts. Supply only the exception type its lookup path
|
|
28
45
|
# rescues so show-page behavior can be exercised with an in-memory fixture.
|
|
@@ -43,14 +60,6 @@ unless defined?(ActiveRecord::Base)
|
|
|
43
60
|
end
|
|
44
61
|
end
|
|
45
62
|
|
|
46
|
-
module TurboFrameTestHelper
|
|
47
|
-
def turbo_frame_tag(name, **options, &block)
|
|
48
|
-
content_tag(:turbo_frame, capture(&block), id: name, **options)
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
ActionView::Base.include(TurboFrameTestHelper)
|
|
53
|
-
|
|
54
63
|
module ReadOnlyResourceFixtures
|
|
55
64
|
class Relation
|
|
56
65
|
include Enumerable
|
|
@@ -142,3 +151,59 @@ module Admin
|
|
|
142
151
|
end
|
|
143
152
|
end
|
|
144
153
|
end
|
|
154
|
+
|
|
155
|
+
# The dummy app is database-free and `ActiveRecord::Base` above is a bare
|
|
156
|
+
# stub class, so every other fixture in this suite is a PORO and
|
|
157
|
+
# `active_record_base?` is false for all of them -- every AR-dependent path
|
|
158
|
+
# (`auto_admin_suite_path_for`, `format_table_cell`'s AR branch) is
|
|
159
|
+
# otherwise unreachable by tests. Subclassing the stub gives a fixture that
|
|
160
|
+
# genuinely satisfies `active_record_base?`, with no database and no
|
|
161
|
+
# railtie required. Established here (Task 3, phase 2b) because it's
|
|
162
|
+
# reused by later tasks (4, 7); those tasks' own test files each `require
|
|
163
|
+
# "test_helper"`, so this is visible to them even when a single test file
|
|
164
|
+
# is run in isolation via `rake test TEST=...` -- unlike a fixture defined
|
|
165
|
+
# only in association_linking_test.rb, which would not be.
|
|
166
|
+
module LinkingFixtures
|
|
167
|
+
class Company < ActiveRecord::Base
|
|
168
|
+
extend ActiveModel::Naming
|
|
169
|
+
|
|
170
|
+
attr_reader :id, :name
|
|
171
|
+
|
|
172
|
+
def initialize(id: 7, name: "Acme Corp")
|
|
173
|
+
@id = id
|
|
174
|
+
@name = name
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def self.all = ReadOnlyResourceFixtures::Relation.new([ new ])
|
|
178
|
+
def self.column_names = %w[id name]
|
|
179
|
+
def self.primary_key = "id"
|
|
180
|
+
def self.columns_hash = { "id" => Struct.new(:type).new(:integer) }
|
|
181
|
+
def self.find(_id) = new
|
|
182
|
+
def to_param = id.to_s
|
|
183
|
+
def attributes = { "id" => id, "name" => name }
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
module Admin
|
|
188
|
+
module Resources
|
|
189
|
+
# Registered so `auto_admin_suite_path_for` can resolve a real path for
|
|
190
|
+
# `LinkingFixtures::Company` instances (it looks up `registered_resources`
|
|
191
|
+
# by `model_class`). Never call `Admin::Base::Resource.reset_registry!` in
|
|
192
|
+
# a test: `inherited` fires only on class creation, so a reset-then-reload
|
|
193
|
+
# would permanently empty the registry for the rest of the run.
|
|
194
|
+
class LinkingCompanyResource < Admin::Base::Resource
|
|
195
|
+
model LinkingFixtures::Company
|
|
196
|
+
portal :ops
|
|
197
|
+
section :observability
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# MCP never permits anonymous callers, including in the development escape hatch.
|
|
203
|
+
class McpIntegrationTest < ActionDispatch::IntegrationTest
|
|
204
|
+
setup do
|
|
205
|
+
@previous_mcp_actor_resolver = AdminSuite.config.current_actor
|
|
206
|
+
AdminSuite.config.current_actor = ->(_) { "test-operator" }
|
|
207
|
+
end
|
|
208
|
+
teardown { AdminSuite.config.current_actor = @previous_mcp_actor_resolver }
|
|
209
|
+
end
|
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.
|
|
4
|
+
version: 0.6.1
|
|
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-
|
|
11
|
+
date: 2026-09-12 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -36,20 +36,54 @@ dependencies:
|
|
|
36
36
|
requirements:
|
|
37
37
|
- - ">="
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: '
|
|
39
|
+
version: '9.0'
|
|
40
|
+
- - "<"
|
|
41
|
+
- !ruby/object:Gem::Version
|
|
42
|
+
version: '10'
|
|
43
|
+
type: :runtime
|
|
44
|
+
prerelease: false
|
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
46
|
+
requirements:
|
|
47
|
+
- - ">="
|
|
48
|
+
- !ruby/object:Gem::Version
|
|
49
|
+
version: '9.0'
|
|
50
|
+
- - "<"
|
|
51
|
+
- !ruby/object:Gem::Version
|
|
52
|
+
version: '10'
|
|
53
|
+
- !ruby/object:Gem::Dependency
|
|
54
|
+
name: mcp
|
|
55
|
+
requirement: !ruby/object:Gem::Requirement
|
|
56
|
+
requirements:
|
|
57
|
+
- - "~>"
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: '1.1'
|
|
60
|
+
type: :runtime
|
|
61
|
+
prerelease: false
|
|
62
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
63
|
+
requirements:
|
|
64
|
+
- - "~>"
|
|
65
|
+
- !ruby/object:Gem::Version
|
|
66
|
+
version: '1.1'
|
|
67
|
+
- !ruby/object:Gem::Dependency
|
|
68
|
+
name: turbo-rails
|
|
69
|
+
requirement: !ruby/object:Gem::Requirement
|
|
70
|
+
requirements:
|
|
71
|
+
- - ">="
|
|
72
|
+
- !ruby/object:Gem::Version
|
|
73
|
+
version: '2.0'
|
|
40
74
|
- - "<"
|
|
41
75
|
- !ruby/object:Gem::Version
|
|
42
|
-
version: '
|
|
76
|
+
version: '3.0'
|
|
43
77
|
type: :runtime
|
|
44
78
|
prerelease: false
|
|
45
79
|
version_requirements: !ruby/object:Gem::Requirement
|
|
46
80
|
requirements:
|
|
47
81
|
- - ">="
|
|
48
82
|
- !ruby/object:Gem::Version
|
|
49
|
-
version: '
|
|
83
|
+
version: '2.0'
|
|
50
84
|
- - "<"
|
|
51
85
|
- !ruby/object:Gem::Version
|
|
52
|
-
version: '
|
|
86
|
+
version: '3.0'
|
|
53
87
|
- !ruby/object:Gem::Dependency
|
|
54
88
|
name: lucide-rails
|
|
55
89
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -126,11 +160,13 @@ files:
|
|
|
126
160
|
- app/assets/builds/admin_suite_tailwind.css
|
|
127
161
|
- app/assets/rouge.css
|
|
128
162
|
- app/assets/tailwind/admin_suite.css
|
|
163
|
+
- app/assets/vendor/chart.umd.min.js
|
|
129
164
|
- app/assets/vendor/easymde.min.css
|
|
130
165
|
- app/assets/vendor/easymde.min.js
|
|
131
166
|
- app/controllers/admin_suite/application_controller.rb
|
|
132
167
|
- app/controllers/admin_suite/dashboard_controller.rb
|
|
133
168
|
- app/controllers/admin_suite/docs_controller.rb
|
|
169
|
+
- app/controllers/admin_suite/mcp_controller.rb
|
|
134
170
|
- app/controllers/admin_suite/portals_controller.rb
|
|
135
171
|
- app/controllers/admin_suite/resources_controller.rb
|
|
136
172
|
- app/helpers/admin_suite/base_helper.rb
|
|
@@ -139,6 +175,7 @@ files:
|
|
|
139
175
|
- app/helpers/admin_suite/resources_helper.rb
|
|
140
176
|
- app/helpers/admin_suite/theme_helper.rb
|
|
141
177
|
- app/javascript/admin_suite_application.js
|
|
178
|
+
- app/javascript/controllers/admin_suite/chart_controller.js
|
|
142
179
|
- app/javascript/controllers/admin_suite/click_actions_controller.js
|
|
143
180
|
- app/javascript/controllers/admin_suite/clipboard_controller.js
|
|
144
181
|
- app/javascript/controllers/admin_suite/code_editor_controller.js
|
|
@@ -183,8 +220,10 @@ files:
|
|
|
183
220
|
- lib/admin_suite.rb
|
|
184
221
|
- lib/admin_suite/auth.rb
|
|
185
222
|
- lib/admin_suite/auth/host_hook.rb
|
|
223
|
+
- lib/admin_suite/auth/host_user.rb
|
|
186
224
|
- lib/admin_suite/auth/http_basic.rb
|
|
187
225
|
- lib/admin_suite/auth/strategy.rb
|
|
226
|
+
- lib/admin_suite/authorization_context.rb
|
|
188
227
|
- lib/admin_suite/configuration.rb
|
|
189
228
|
- lib/admin_suite/definition_loader.rb
|
|
190
229
|
- lib/admin_suite/deprecation.rb
|
|
@@ -192,14 +231,21 @@ files:
|
|
|
192
231
|
- lib/admin_suite/host_autoload_policy.rb
|
|
193
232
|
- lib/admin_suite/legacy_custom_renderer_procs.rb
|
|
194
233
|
- lib/admin_suite/markdown_renderer.rb
|
|
234
|
+
- lib/admin_suite/mcp.rb
|
|
235
|
+
- lib/admin_suite/mcp/authorization.rb
|
|
236
|
+
- lib/admin_suite/mcp/serializer.rb
|
|
237
|
+
- lib/admin_suite/mcp/tools/aggregate.rb
|
|
238
|
+
- lib/admin_suite/mcp/tools/describe_resources.rb
|
|
239
|
+
- lib/admin_suite/mcp/tools/get_record.rb
|
|
240
|
+
- lib/admin_suite/mcp/tools/list_records.rb
|
|
195
241
|
- lib/admin_suite/portal_definition.rb
|
|
196
242
|
- lib/admin_suite/portal_registry.rb
|
|
243
|
+
- lib/admin_suite/query.rb
|
|
197
244
|
- lib/admin_suite/renderer.rb
|
|
198
245
|
- lib/admin_suite/renderer_registry.rb
|
|
199
246
|
- lib/admin_suite/renderers/code_renderer.rb
|
|
200
247
|
- lib/admin_suite/renderers/json_renderer.rb
|
|
201
248
|
- lib/admin_suite/renderers/key_value_renderer.rb
|
|
202
|
-
- lib/admin_suite/renderers/legacy_gleania.rb
|
|
203
249
|
- lib/admin_suite/renderers/table_from_renderer.rb
|
|
204
250
|
- lib/admin_suite/section_definition.rb
|
|
205
251
|
- lib/admin_suite/theme_palette.rb
|
|
@@ -260,32 +306,53 @@ files:
|
|
|
260
306
|
- test/dummy/public/robots.txt
|
|
261
307
|
- test/dummy/test/test_helper.rb
|
|
262
308
|
- test/fixtures/docs/progress/PROGRESS_REPORT.md
|
|
309
|
+
- test/integration/association_linking_test.rb
|
|
263
310
|
- test/integration/authentication_test.rb
|
|
264
311
|
- test/integration/authorization_test.rb
|
|
312
|
+
- test/integration/chart_panel_test.rb
|
|
265
313
|
- test/integration/dashboard_test.rb
|
|
266
314
|
- test/integration/docs_test.rb
|
|
315
|
+
- test/integration/index_query_characterization_test.rb
|
|
316
|
+
- test/integration/index_table_test.rb
|
|
267
317
|
- test/integration/layout_assets_test.rb
|
|
318
|
+
- test/integration/mcp_aggregate_test.rb
|
|
319
|
+
- test/integration/mcp_authorization_test.rb
|
|
320
|
+
- test/integration/mcp_endpoint_test.rb
|
|
321
|
+
- test/integration/mcp_get_record_test.rb
|
|
322
|
+
- test/integration/mcp_instrumentation_test.rb
|
|
323
|
+
- test/integration/mcp_list_records_test.rb
|
|
324
|
+
- test/integration/mcp_parity_test.rb
|
|
325
|
+
- test/integration/mcp_release_test.rb
|
|
268
326
|
- test/integration/navigation_sections_test.rb
|
|
269
327
|
- test/integration/pagination_and_stats_test.rb
|
|
270
328
|
- test/integration/read_only_resource_test.rb
|
|
329
|
+
- test/integration/searchable_select_search_test.rb
|
|
330
|
+
- test/integration/show_hide_blank_test.rb
|
|
271
331
|
- test/integration/theme_test.rb
|
|
332
|
+
- test/integration/toggle_test.rb
|
|
272
333
|
- test/lib/action_executor_redirect_test.rb
|
|
273
334
|
- test/lib/action_executor_test.rb
|
|
335
|
+
- test/lib/auth_host_user_test.rb
|
|
274
336
|
- test/lib/auth_http_basic_test.rb
|
|
275
337
|
- test/lib/auth_resolution_test.rb
|
|
276
338
|
- test/lib/auth_test.rb
|
|
339
|
+
- test/lib/authorization_context_test.rb
|
|
277
340
|
- test/lib/builtin_renderers_test.rb
|
|
278
341
|
- test/lib/definition_loader_test.rb
|
|
279
342
|
- test/lib/engine_defaults_test.rb
|
|
280
343
|
- test/lib/form_field_renderer_test.rb
|
|
281
|
-
- test/lib/
|
|
344
|
+
- test/lib/format_table_cell_test.rb
|
|
345
|
+
- test/lib/index_includes_test.rb
|
|
282
346
|
- test/lib/markdown_renderer_test.rb
|
|
347
|
+
- test/lib/mcp_serializer_test.rb
|
|
348
|
+
- test/lib/query_test.rb
|
|
349
|
+
- test/lib/removed_deprecations_test.rb
|
|
283
350
|
- test/lib/renderer_test.rb
|
|
284
|
-
- test/lib/resource_exportable_deprecation_test.rb
|
|
285
351
|
- test/lib/resource_observability_extensions_test.rb
|
|
286
352
|
- test/lib/show_value_formatter_test.rb
|
|
287
353
|
- test/lib/theme_palette_test.rb
|
|
288
354
|
- test/lib/zeitwerk_integration_test.rb
|
|
355
|
+
- test/publish_workflow_test.rb
|
|
289
356
|
- test/test_helper.rb
|
|
290
357
|
homepage: https://github.com/techwright-lab/admin_suite
|
|
291
358
|
licenses:
|
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module AdminSuite
|
|
4
|
-
module Renderers
|
|
5
|
-
# The four Gleania-specific LLM chat-transcript renderers, moved verbatim
|
|
6
|
-
# out of `AdminSuite::BaseHelper` (role-coloured message bubbles, tool-call
|
|
7
|
-
# panels, prompt-template mustache highlighting).
|
|
8
|
-
#
|
|
9
|
-
# These are deprecated: they still work in 0.4.0 (logging a warning once
|
|
10
|
-
# per key per process the first time each is used), and are DELETED in
|
|
11
|
-
# 0.5.0 (Phase 2b). Gleania is expected to migrate to host-side renderer
|
|
12
|
-
# classes under `app/admin/renderers/*.rb` before then.
|
|
13
|
-
#
|
|
14
|
-
# The renderer bodies below are intentionally byte-equivalent to the
|
|
15
|
-
# BaseHelper methods they replace, other than `resource` -> `record` and
|
|
16
|
-
# routing BaseHelper method calls (`render_json_block`, `simple_format`,
|
|
17
|
-
# `concat`) through `view.` — this class is not `included` into the view,
|
|
18
|
-
# so those calls can't resolve as bare method sends the way they did
|
|
19
|
-
# inside the helper module. Do not "improve" this markup: a byte-for-byte
|
|
20
|
-
# move keeps gleania's Assistant/AI portal pages pixel-identical, and
|
|
21
|
-
# makes the 0.5.0 deletion a clean removal.
|
|
22
|
-
module LegacyGleania
|
|
23
|
-
extend AdminSuite::Deprecation
|
|
24
|
-
|
|
25
|
-
DEPRECATION_MESSAGE_FORMAT =
|
|
26
|
-
"AdminSuite: the :%<key>s renderer is deprecated and will be removed in 0.5.0. " \
|
|
27
|
-
"Move it to app/admin/renderers in your app."
|
|
28
|
-
|
|
29
|
-
class << self
|
|
30
|
-
# Fires the deprecation sink at most once per `key` per process.
|
|
31
|
-
# `warn_once_sink` and `reset_deprecation_notices!` come from
|
|
32
|
-
# `AdminSuite::Deprecation`, extended above.
|
|
33
|
-
#
|
|
34
|
-
# @param key [Symbol]
|
|
35
|
-
# @return [void]
|
|
36
|
-
def warn_once(key)
|
|
37
|
-
super(key, format(DEPRECATION_MESSAGE_FORMAT, key: key))
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
# Verbatim from `BaseHelper#render_prompt_template`.
|
|
42
|
-
class PromptTemplateRenderer < Renderer
|
|
43
|
-
def render
|
|
44
|
-
LegacyGleania.warn_once(:prompt_template_preview)
|
|
45
|
-
|
|
46
|
-
template = record.respond_to?(:prompt_template) ? record.prompt_template : nil
|
|
47
|
-
return content_tag(:p, "No template defined", class: "text-slate-500 italic") if template.blank?
|
|
48
|
-
|
|
49
|
-
highlighted_template = h(template).gsub(/\{\{(\w+)\}\}/) do
|
|
50
|
-
"<span class=\"text-amber-400 bg-amber-900/30 px-1 rounded\">{{#{$1}}}</span>"
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
content_tag(:div, class: "relative group") do
|
|
54
|
-
view.concat(content_tag(:div, class: "absolute top-2 right-2 flex items-center gap-2") do
|
|
55
|
-
view.concat(content_tag(:span, "TEMPLATE", class: "text-xs font-medium text-slate-400 uppercase tracking-wider"))
|
|
56
|
-
view.concat(content_tag(:button,
|
|
57
|
-
'<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/></svg>'.html_safe,
|
|
58
|
-
type: "button",
|
|
59
|
-
class: "p-1 text-slate-400 hover:text-slate-600 opacity-0 group-hover:opacity-100 transition-opacity",
|
|
60
|
-
data: { controller: "admin-suite--clipboard", action: "click->admin-suite--clipboard#copy", "admin-suite--clipboard-text-value": template },
|
|
61
|
-
title: "Copy to clipboard"))
|
|
62
|
-
end)
|
|
63
|
-
|
|
64
|
-
view.concat(content_tag(:pre, class: "bg-slate-900 text-slate-100 p-4 rounded-lg overflow-x-auto text-sm font-mono max-h-[600px] overflow-y-auto whitespace-pre-wrap leading-relaxed") do
|
|
65
|
-
highlighted_template.html_safe
|
|
66
|
-
end)
|
|
67
|
-
|
|
68
|
-
variables = template.scan(/\{\{(\w+)\}\}/).flatten.uniq
|
|
69
|
-
if variables.any?
|
|
70
|
-
view.concat(content_tag(:div, class: "mt-3 pt-3 border-t border-slate-700") do
|
|
71
|
-
view.concat(content_tag(:span, "Variables: ", class: "text-sm text-slate-400"))
|
|
72
|
-
view.concat(content_tag(:div, class: "inline-flex flex-wrap gap-1 mt-1") do
|
|
73
|
-
variables.each do |var|
|
|
74
|
-
view.concat(content_tag(:code, "{{#{var}}}", class: "text-xs px-2 py-0.5 bg-amber-900/30 text-amber-400 rounded"))
|
|
75
|
-
end
|
|
76
|
-
end)
|
|
77
|
-
end)
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
# Verbatim from `BaseHelper#render_messages_preview`.
|
|
84
|
-
class MessagesPreviewRenderer < Renderer
|
|
85
|
-
def render
|
|
86
|
-
LegacyGleania.warn_once(:messages_preview)
|
|
87
|
-
|
|
88
|
-
messages = record.respond_to?(:messages) ? record.messages : []
|
|
89
|
-
if messages.respond_to?(:chronological)
|
|
90
|
-
messages = messages.chronological
|
|
91
|
-
end
|
|
92
|
-
messages = messages.limit(50) if messages.respond_to?(:limit)
|
|
93
|
-
messages = Array.wrap(messages)
|
|
94
|
-
|
|
95
|
-
return content_tag(:p, "No messages", class: "text-slate-500 italic") if messages.blank?
|
|
96
|
-
|
|
97
|
-
content_tag(:div, class: "space-y-4 max-h-[600px] overflow-y-auto -mx-6 -mb-6 p-6 pt-0") do
|
|
98
|
-
messages.each_with_index do |msg, idx|
|
|
99
|
-
if msg.respond_to?(:role)
|
|
100
|
-
role = msg.role
|
|
101
|
-
content = msg.content
|
|
102
|
-
created_at = msg.respond_to?(:created_at) ? msg.created_at : nil
|
|
103
|
-
else
|
|
104
|
-
role = msg["role"] || msg[:role] || "unknown"
|
|
105
|
-
content = msg["content"] || msg[:content] || ""
|
|
106
|
-
created_at = msg["created_at"] || msg[:created_at]
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
role_class = case role.to_s
|
|
110
|
-
when "user" then "bg-blue-50 border-blue-200"
|
|
111
|
-
when "assistant" then "bg-emerald-50 border-emerald-200"
|
|
112
|
-
when "tool" then "bg-amber-50 border-amber-200"
|
|
113
|
-
when "system" then "bg-slate-50 border-slate-200"
|
|
114
|
-
else "bg-slate-50 border-slate-200"
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
role_icon = case role.to_s
|
|
118
|
-
when "user"
|
|
119
|
-
'<svg class="w-4 h-4 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/></svg>'.html_safe
|
|
120
|
-
when "assistant"
|
|
121
|
-
'<svg class="w-4 h-4 text-emerald-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z"/></svg>'.html_safe
|
|
122
|
-
when "tool"
|
|
123
|
-
'<svg class="w-4 h-4 text-amber-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/></svg>'.html_safe
|
|
124
|
-
else
|
|
125
|
-
'<svg class="w-4 h-4 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/></svg>'.html_safe
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
view.concat(content_tag(:div, class: "rounded-lg border p-4 #{role_class}") do
|
|
129
|
-
view.concat(content_tag(:div, class: "flex items-center justify-between mb-3") do
|
|
130
|
-
view.concat(content_tag(:div, class: "flex items-center gap-2") do
|
|
131
|
-
view.concat(role_icon)
|
|
132
|
-
view.concat(content_tag(:span, role.to_s.capitalize, class: "text-sm font-medium text-slate-700"))
|
|
133
|
-
end)
|
|
134
|
-
view.concat(content_tag(:div, class: "flex items-center gap-2 text-xs text-slate-400") do
|
|
135
|
-
view.concat(content_tag(:span, created_at.strftime("%H:%M:%S"))) if created_at.respond_to?(:strftime)
|
|
136
|
-
view.concat(content_tag(:span, "##{idx + 1}"))
|
|
137
|
-
end)
|
|
138
|
-
end)
|
|
139
|
-
|
|
140
|
-
content_str = content.to_s
|
|
141
|
-
if role.to_s == "tool" && content_str.start_with?("{", "[")
|
|
142
|
-
begin
|
|
143
|
-
parsed = JSON.parse(content_str)
|
|
144
|
-
view.concat(view.render_json_block(parsed))
|
|
145
|
-
rescue JSON::ParserError
|
|
146
|
-
view.concat(content_tag(:div, view.simple_format(h(content_str)), class: "prose prose-sm max-w-none"))
|
|
147
|
-
end
|
|
148
|
-
else
|
|
149
|
-
view.concat(content_tag(:div, view.simple_format(h(content_str)), class: "prose prose-sm max-w-none"))
|
|
150
|
-
end
|
|
151
|
-
end)
|
|
152
|
-
end
|
|
153
|
-
end
|
|
154
|
-
end
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
# Verbatim from `BaseHelper#render_tool_args_preview`.
|
|
158
|
-
class ToolArgsRenderer < Renderer
|
|
159
|
-
def render
|
|
160
|
-
LegacyGleania.warn_once(:tool_args_preview)
|
|
161
|
-
|
|
162
|
-
args = record.respond_to?(:args) ? record.args : (record.respond_to?(:arguments) ? record.arguments : {})
|
|
163
|
-
result = record.respond_to?(:result) ? record.result : nil
|
|
164
|
-
error = record.respond_to?(:error) ? record.error : nil
|
|
165
|
-
|
|
166
|
-
content_tag(:div, class: "space-y-6") do
|
|
167
|
-
view.concat(content_tag(:div) do
|
|
168
|
-
view.concat(content_tag(:h4, "Arguments", class: "text-sm font-medium text-slate-500 mb-2"))
|
|
169
|
-
if args.present? && args != {}
|
|
170
|
-
view.concat(view.render_json_block(args))
|
|
171
|
-
else
|
|
172
|
-
view.concat(content_tag(:p, "No arguments", class: "text-slate-400 italic text-sm"))
|
|
173
|
-
end
|
|
174
|
-
end)
|
|
175
|
-
|
|
176
|
-
if result.present? && result != {}
|
|
177
|
-
view.concat(content_tag(:div, class: "pt-4 border-t border-slate-200") do
|
|
178
|
-
view.concat(content_tag(:h4, "Result", class: "text-sm font-medium text-slate-500 mb-2"))
|
|
179
|
-
view.concat(view.render_json_block(result))
|
|
180
|
-
end)
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
if error.present?
|
|
184
|
-
view.concat(content_tag(:div, class: "pt-4 border-t border-slate-200") do
|
|
185
|
-
view.concat(content_tag(:h4, "Error", class: "text-sm font-medium text-red-500 mb-2"))
|
|
186
|
-
view.concat(content_tag(:div, class: "bg-red-50 border border-red-200 rounded-lg p-4") do
|
|
187
|
-
content_tag(:pre, h(error.to_s), class: "text-sm text-red-700 whitespace-pre-wrap font-mono")
|
|
188
|
-
end)
|
|
189
|
-
end)
|
|
190
|
-
end
|
|
191
|
-
end
|
|
192
|
-
end
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
# Verbatim from `BaseHelper#render_turn_messages_preview`.
|
|
196
|
-
class TurnMessagesRenderer < Renderer
|
|
197
|
-
def render
|
|
198
|
-
LegacyGleania.warn_once(:turn_messages_preview)
|
|
199
|
-
|
|
200
|
-
user_msg = record.respond_to?(:user_message) ? record.user_message : nil
|
|
201
|
-
asst_msg = record.respond_to?(:assistant_message) ? record.assistant_message : nil
|
|
202
|
-
|
|
203
|
-
content_tag(:div, class: "space-y-4") do
|
|
204
|
-
if user_msg
|
|
205
|
-
view.concat(content_tag(:div, class: "rounded-lg border p-4 bg-blue-50 border-blue-200") do
|
|
206
|
-
view.concat(content_tag(:div, class: "flex items-center gap-2 mb-2") do
|
|
207
|
-
view.concat('<svg class="w-4 h-4 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/></svg>'.html_safe)
|
|
208
|
-
view.concat(content_tag(:span, "User", class: "text-sm font-medium text-slate-700"))
|
|
209
|
-
end)
|
|
210
|
-
view.concat(content_tag(:div, view.simple_format(h(user_msg.respond_to?(:content) ? user_msg.content.to_s : user_msg.to_s)), class: "prose prose-sm max-w-none"))
|
|
211
|
-
end)
|
|
212
|
-
end
|
|
213
|
-
|
|
214
|
-
if asst_msg
|
|
215
|
-
view.concat(content_tag(:div, class: "rounded-lg border p-4 bg-emerald-50 border-emerald-200") do
|
|
216
|
-
view.concat(content_tag(:div, class: "flex items-center gap-2 mb-2") do
|
|
217
|
-
view.concat('<svg class="w-4 h-4 text-emerald-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z"/></svg>'.html_safe)
|
|
218
|
-
view.concat(content_tag(:span, "Assistant", class: "text-sm font-medium text-slate-700"))
|
|
219
|
-
end)
|
|
220
|
-
view.concat(content_tag(:div, view.simple_format(h(asst_msg.respond_to?(:content) ? asst_msg.content.to_s : asst_msg.to_s)), class: "prose prose-sm max-w-none"))
|
|
221
|
-
end)
|
|
222
|
-
end
|
|
223
|
-
|
|
224
|
-
view.concat(content_tag(:p, "No messages found", class: "text-slate-400 italic text-sm")) unless user_msg || asst_msg
|
|
225
|
-
end
|
|
226
|
-
end
|
|
227
|
-
end
|
|
228
|
-
end
|
|
229
|
-
end
|
|
230
|
-
end
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "test_helper"
|
|
4
|
-
|
|
5
|
-
module AdminSuite
|
|
6
|
-
class LegacyRendererDeprecationTest < ActionView::TestCase
|
|
7
|
-
include ::ERB::Util
|
|
8
|
-
include AdminSuite::BaseHelper
|
|
9
|
-
|
|
10
|
-
Prompt = Struct.new(:prompt_template)
|
|
11
|
-
|
|
12
|
-
setup { AdminSuite::Renderers::LegacyGleania.reset_deprecation_notices! }
|
|
13
|
-
|
|
14
|
-
test "the legacy prompt renderer still renders its template" do
|
|
15
|
-
html = render_custom_section(Prompt.new("Hello {{name}}"), :prompt_template_preview)
|
|
16
|
-
assert_includes html, "Hello"
|
|
17
|
-
assert_includes html, "name"
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
test "using a legacy renderer logs a deprecation once per key" do
|
|
21
|
-
logged = []
|
|
22
|
-
AdminSuite::Renderers::LegacyGleania.stub(:warn_once_sink, ->(msg) { logged << msg }) do
|
|
23
|
-
2.times { render_custom_section(Prompt.new("x"), :prompt_template_preview) }
|
|
24
|
-
end
|
|
25
|
-
assert_equal 1, logged.size
|
|
26
|
-
assert_includes logged.first, "deprecated"
|
|
27
|
-
assert_includes logged.first, "0.5.0"
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
test "the gem no longer includes the host CustomRenderersHelper constant" do
|
|
31
|
-
source = AdminSuite::Engine.root.join("app/helpers/admin_suite/base_helper.rb").read
|
|
32
|
-
refute_includes source, "Internal::Developer::CustomRenderersHelper"
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "test_helper"
|
|
4
|
-
|
|
5
|
-
module AdminSuite
|
|
6
|
-
class ResourceExportableDeprecationTest < ActiveSupport::TestCase
|
|
7
|
-
# Regression fixture for Finding 1 of the whole-branch review: Task 8
|
|
8
|
-
# removed `exportable` after grepping the *gem* for readers, but host
|
|
9
|
-
# apps are *callers* of this DSL (gleania: 30 resource files;
|
|
10
|
-
# trust_growth: 1). A real removal raises `NoMethodError` at
|
|
11
|
-
# definition-load time -- and in production `DefinitionLoader` logs and
|
|
12
|
-
# swallows that, so the resource silently vanishes from the admin.
|
|
13
|
-
class LegacyExportableResource < Admin::Base::Resource
|
|
14
|
-
exportable :json, :csv
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
setup { LegacyExportableResource.reset_deprecation_notices! }
|
|
18
|
-
|
|
19
|
-
test "calling exportable in a resource body is harmless" do
|
|
20
|
-
assert_nothing_raised { LegacyExportableResource.exportable(:json, :csv) }
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
test "exportable does not restore any export-format reader" do
|
|
24
|
-
refute LegacyExportableResource.respond_to?(:export_formats)
|
|
25
|
-
refute LegacyExportableResource.instance_variable_defined?(:@export_formats)
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
test "exportable warns once per resource class, naming the class and the 0.5.0 removal" do
|
|
29
|
-
logged = []
|
|
30
|
-
LegacyExportableResource.stub(:warn_once_sink, ->(msg) { logged << msg }) do
|
|
31
|
-
2.times { LegacyExportableResource.exportable(:json, :csv) }
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
assert_equal 1, logged.size
|
|
35
|
-
assert_includes logged.first, "LegacyExportableResource"
|
|
36
|
-
assert_includes logged.first, "0.5.0"
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
end
|