From 3121d3e3a8416db97a2679ead86fee745ad0a9ad Mon Sep 17 00:00:00 2001 From: "Daniel (dB.) Doubrovkine" Date: Sun, 25 Oct 2026 16:15:31 -0400 Subject: [PATCH] Provide collision-free framework test fabricators Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CHANGELOG.md | 1 + README.md | 23 ++++++ UPGRADING.md | 16 ++++ lib/slack-ruby-bot-server/rspec.rb | 4 +- .../rspec/fabricators.rb | 4 + .../fabricators/slack_ruby_bot_server_team.rb | 5 ++ .../rspec/fabricators/team.rb | 8 +- spec/slack-ruby-bot-server/rspec_spec.rb | 77 +++++++++++++++++++ 8 files changed, 130 insertions(+), 8 deletions(-) create mode 100644 lib/slack-ruby-bot-server/rspec/fabricators.rb create mode 100644 lib/slack-ruby-bot-server/rspec/fabricators/slack_ruby_bot_server_team.rb create mode 100644 spec/slack-ruby-bot-server/rspec_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index fb5c208..dde1240 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### 2.3.0 (Next) * [#188](https://github.com/slack-ruby/slack-ruby-bot-server/pull/188): Reduce the database CI matrices from 22 to 12 jobs while retaining representative legacy ORM majors and modern Ruby/Grape 3/4 coverage - [@dblock](https://github.com/dblock), [@Copilot](https://github.com/apps/copilot-swe-agent). +* [#189](https://github.com/slack-ruby/slack-ruby-bot-server/pull/189): Add a prefixed Team fabricator entry point and preserve application-defined `:team` fabricators in the legacy RSpec helper - [@dblock](https://github.com/dblock), [@Copilot](https://github.com/apps/copilot-swe-agent). * [#186](https://github.com/slack-ruby/slack-ruby-bot-server/pull/186): Document persisted custom Team attributes for both adapters and Rails concern loading/reloading - [@dblock](https://github.com/dblock), [@Copilot](https://github.com/apps/copilot-swe-agent). * [#183](https://github.com/slack-ruby/slack-ruby-bot-server/pull/183): Add a `config.server_pages` block to configure page middleware options centrally without changing the installed library's defaults - [@dblock](https://github.com/dblock), [@Copilot](https://github.com/apps/copilot-swe-agent). * [#184](https://github.com/slack-ruby/slack-ruby-bot-server/pull/184): Add a configurable API application and separately mountable team read/registration endpoints, preserving the default API while supporting custom routes and registration-only applications - [@dblock](https://github.com/dblock), [@Copilot](https://github.com/apps/copilot-swe-agent). diff --git a/README.md b/README.md index a4d915e..24c5d82 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ Build a complete Slack bot service with Slack button integration, in Ruby. - [HTML Templates](#html-templates) - [Page Middleware Configuration](#page-middleware-configuration) - [Access Tokens](#access-tokens) +- [Testing](#testing) - [Sample Bots Using Slack Ruby Bot Server](#sample-bots-using-slack-ruby-bot-server) - [Copyright & License](#copyright--license) @@ -459,6 +460,28 @@ Without a block, the framework preserves the installed library's defaults and ex By default the implementation of [Team](lib/slack-ruby-bot-server/models/team) stores the value of the token with all the requested OAuth scopes in both `token` and `activated_user_access_token` (for backwards compatibility), along with `oauth_version` and `oauth_scope`. If a legacy Slack bot integration `bot_access_token` is present, it is stored as `token`, and `activated_user_access_token` is the token that has all the requested OAuth scopes. +## Testing + +The optional test helpers use [Fabrication](https://fabricationgem.org/) and Faker. Add `fabrication` and `faker` to your application's test dependencies; neither is a runtime dependency of this gem. + +To use the framework's installation defaults without reserving your application's `:team` fabricator name, load the prefixed-only helper: + +```ruby +require 'slack-ruby-bot-server/rspec/fabricators' + +team = Fabricate(:slack_ruby_bot_server_team) +``` + +This fabricator creates the existing `::Team` model with unique token/team ID sequences and a generated name. Applications can register their own `:team` before or after this helper, or inherit the framework defaults: + +```ruby +Fabricator(:team, from: :slack_ruby_bot_server_team) do + name { 'My application team' } +end +``` + +The existing `require 'slack-ruby-bot-server/rspec'` remains supported. It loads the prefixed fabricator and registers the legacy `:team` alias only when no `:team` definition already exists. If your application defines `:team` later, replace the legacy require with `slack-ruby-bot-server/rspec/fabricators`; loading both entry points still opts into the legacy alias. + ## Sample Bots Using Slack Ruby Bot Server * [slack-ruby-bot-server-events-sample](https://github.com/slack-ruby/slack-ruby-bot-server-events-sample), a generic sample diff --git a/UPGRADING.md b/UPGRADING.md index 2f1e6e6..c4b25d8 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -3,6 +3,22 @@ Upgrading Slack-Ruby-Bot-Server ### Upgrading to >= 2.3.0 +#### Recommended Test Helper Migration + +If your application defines its own `:team` fabricator, replace `require 'slack-ruby-bot-server/rspec'` with `require 'slack-ruby-bot-server/rspec/fabricators'`. This is recommended, not required: the new entry point registers only `:slack_ruby_bot_server_team`, leaving `:team` available regardless of whether your application's fabricator is loaded before or after the helper. + +Use `Fabricate(:slack_ruby_bot_server_team)` for the framework defaults, or inherit them in your application's fabricator: + +```ruby +require 'slack-ruby-bot-server/rspec/fabricators' + +Fabricator(:team, from: :slack_ruby_bot_server_team) do + name { 'My application team' } +end +``` + +The legacy helper remains supported and registers `:team` only if no definition already exists. Replace the old require rather than adding the new one alongside it; loading both still opts into the legacy alias and can conflict with a `:team` definition loaded later. + #### Status Checks and Inactive Installations `GET /api/status` now pings only active installations. When Slack returns `account_inactive` or `invalid_auth`, the installation is deactivated through `Service#deactivate!`, and the endpoint returns HTTP `200` with `ping: { "ok": false, "error": "account_inactive" }` (or `"invalid_auth"`). The active-team count reflects that deactivation. Monitoring should inspect the failed `ping` as well as the HTTP status. diff --git a/lib/slack-ruby-bot-server/rspec.rb b/lib/slack-ruby-bot-server/rspec.rb index 3e38133..0a06073 100644 --- a/lib/slack-ruby-bot-server/rspec.rb +++ b/lib/slack-ruby-bot-server/rspec.rb @@ -1,4 +1,2 @@ -require 'slack-ruby-bot-server' -require 'fabrication' -require 'faker' +require_relative 'rspec/fabricators' require_relative 'rspec/fabricators/team' diff --git a/lib/slack-ruby-bot-server/rspec/fabricators.rb b/lib/slack-ruby-bot-server/rspec/fabricators.rb new file mode 100644 index 0000000..e797810 --- /dev/null +++ b/lib/slack-ruby-bot-server/rspec/fabricators.rb @@ -0,0 +1,4 @@ +require 'slack-ruby-bot-server' +require 'fabrication' +require 'faker' +require_relative 'fabricators/slack_ruby_bot_server_team' diff --git a/lib/slack-ruby-bot-server/rspec/fabricators/slack_ruby_bot_server_team.rb b/lib/slack-ruby-bot-server/rspec/fabricators/slack_ruby_bot_server_team.rb new file mode 100644 index 0000000..beb6253 --- /dev/null +++ b/lib/slack-ruby-bot-server/rspec/fabricators/slack_ruby_bot_server_team.rb @@ -0,0 +1,5 @@ +Fabricator(:slack_ruby_bot_server_team, class_name: 'Team') do + token { Fabricate.sequence(:team_token) { |i| "abc-#{i}" } } + team_id { Fabricate.sequence(:team_id) { |i| "T#{i}" } } + name { Faker::Lorem.word } +end diff --git a/lib/slack-ruby-bot-server/rspec/fabricators/team.rb b/lib/slack-ruby-bot-server/rspec/fabricators/team.rb index 4262eb9..5b0b84f 100644 --- a/lib/slack-ruby-bot-server/rspec/fabricators/team.rb +++ b/lib/slack-ruby-bot-server/rspec/fabricators/team.rb @@ -1,5 +1,3 @@ -Fabricator(:team) do - token { Fabricate.sequence(:team_token) { |i| "abc-#{i}" } } - team_id { Fabricate.sequence(:team_id) { |i| "T#{i}" } } - name { Faker::Lorem.word } -end +require_relative 'slack_ruby_bot_server_team' + +Fabricator(:team, from: :slack_ruby_bot_server_team) unless Fabrication.manager[:team] diff --git a/spec/slack-ruby-bot-server/rspec_spec.rb b/spec/slack-ruby-bot-server/rspec_spec.rb new file mode 100644 index 0000000..27cdf30 --- /dev/null +++ b/spec/slack-ruby-bot-server/rspec_spec.rb @@ -0,0 +1,77 @@ +require 'spec_helper' +require 'open3' +require 'rbconfig' + +describe 'RSpec fabricators' do + let(:application_fabricator) do + <<~RUBY + Fabricator(:team, class_name: 'Team') do + name { 'Application team' } + token { 'application-token' } + team_id { 'TAPPLICATION' } + end + RUBY + end + + def fabricator_names_after(code) + script = <<~RUBY + require 'logger' + Bundler.require + require 'fabrication' + require 'json' + if defined?(ActiveRecord::Base) + ActiveRecord::Base.establish_connection(ENV.fetch('DATABASE_URL')) + end + #{code} + puts JSON.generate( + application: Fabricate.build(:team).name, + framework: Fabricate.build(:slack_ruby_bot_server_team, name: 'Framework team').name + ) + RUBY + output, error, status = Open3.capture3(RbConfig.ruby, '-Ilib', '-rbundler/setup', '-e', script) + expect(status.success?).to be(true), "#{output}\n#{error}" + JSON.parse(output.lines.last) + end + + it 'persists framework installations with unique credentials independently of the legacy name' do + first = Fabricate(:slack_ruby_bot_server_team) + second = Fabricate(:slack_ruby_bot_server_team) + expect(Team.find(first.id).token).to eq first.token + expect(first.token).not_to eq second.token + expect(first.team_id).not_to eq second.team_id + end + + it 'keeps the legacy helper working without an application fabricator' do + names = fabricator_names_after("require 'slack-ruby-bot-server/rspec'") + expect(names['application']).not_to be_empty + expect(names['framework']).to eq 'Framework team' + end + + it 'preserves an application fabricator registered before the legacy helper' do + names = fabricator_names_after(<<~RUBY) + #{application_fabricator} + require 'slack-ruby-bot-server/rspec' + RUBY + expect(names).to eq('application' => 'Application team', 'framework' => 'Framework team') + end + + it 'allows applications to inherit and customize framework defaults' do + names = fabricator_names_after(<<~RUBY) + require 'slack-ruby-bot-server/rspec/fabricators' + Fabricator(:team, from: :slack_ruby_bot_server_team) do + name { 'Application team' } + end + RUBY + expect(names).to eq('application' => 'Application team', 'framework' => 'Framework team') + end + + %w[before after].each do |order| + it "allows an application fabricator #{order} the prefixed-only helper" do + setup = [application_fabricator, "require 'slack-ruby-bot-server/rspec/fabricators'"] + setup.reverse! if order == 'after' + expect(fabricator_names_after(setup.join("\n"))).to eq( + 'application' => 'Application team', 'framework' => 'Framework team' + ) + end + end +end