From 592251f13c7c711ac9363cc68e151a5c1224bb79 Mon Sep 17 00:00:00 2001 From: "Daniel (dB.) Doubrovkine" Date: Sat, 24 Oct 2026 18:53:11 -0400 Subject: [PATCH] Modernize CI and add Ruby 4 and Grape 4 coverage Retain existing compatibility combinations while adding current ORM/database versions, native service containers, dependency caching, and maintained browser setup. Update pagination test query decoding for Ruby 4. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/rubocop.yml | 12 ++- .github/workflows/test-mongodb.yml | 70 ++++++++++------- .github/workflows/test-postgresql.yml | 78 ++++++++++++------- AGENTS.md | 55 +++++++++++++ CHANGELOG.md | 1 + CONTRIBUTING.md | 15 ++-- Gemfile | 2 + .../endpoints/it_behaves_like_a_cursor_api.rb | 4 +- 8 files changed, 170 insertions(+), 67 deletions(-) create mode 100644 AGENTS.md diff --git a/.github/workflows/rubocop.yml b/.github/workflows/rubocop.yml index 06ac8f3..ca78858 100644 --- a/.github/workflows/rubocop.yml +++ b/.github/workflows/rubocop.yml @@ -1,16 +1,22 @@ --- name: lint on: [push, pull_request] +permissions: + contents: read +concurrency: + group: rubocop-${{ github.ref }} + cancel-in-progress: true jobs: lint: name: RuboCop - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 + timeout-minutes: 10 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v7 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: 3.4 + ruby-version: '4.0' bundler-cache: true - name: Run RuboCop run: bundle exec rubocop \ No newline at end of file diff --git a/.github/workflows/test-mongodb.yml b/.github/workflows/test-mongodb.yml index d296f92..796dbf3 100644 --- a/.github/workflows/test-mongodb.yml +++ b/.github/workflows/test-mongodb.yml @@ -1,40 +1,56 @@ --- -name: test +name: MongoDB on: [push, pull_request] +permissions: + contents: read +concurrency: + group: mongodb-${{ github.ref }} + cancel-in-progress: true jobs: test: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 + timeout-minutes: 20 + env: + DATABASE_ADAPTER: mongoid + MONGOID_VERSION: ${{ matrix.entry.mongoid }} + GRAPE_VERSION: ${{ matrix.entry.grape || '>= 0' }} + JSON_VERSION: ${{ matrix.entry.json }} strategy: + fail-fast: false matrix: entry: - - { ruby: 3.2, mongoid: 6.4.8, mongodb: 4.4 } - - { ruby: 3.2, mongoid: 7.2.3, mongodb: 4.4 } - - { ruby: 3.2, mongoid: 7.3.0, mongodb: 4.4 } - - { ruby: 3.2, mongoid: 7.3.0, mongodb: 4.4 } - - { ruby: 3.3, mongoid: 7.3.0, mongodb: 5.0 } - - { ruby: 3.4, mongoid: 7.5.2, mongodb: 5.0 } - - { ruby: 3.4, mongoid: 8.1.11, mongodb: 6.0 } - - { ruby: 3.4, mongoid: 8.1.11, mongodb: 7.0 } - - { ruby: 3.4, mongoid: 8.1.11, mongodb: 8.0 } - name: test (ruby=${{ matrix.entry.ruby }}, mongoid=${{ matrix.entry.mongoid }}, mongodb=${{ matrix.entry.mongodb }}) + # These Active Support versions pass quirks_mode, removed in JSON 3. + - { ruby: '3.2', mongoid: '6.4.8', mongodb: '4.4', json: '~> 2.0' } + - { ruby: '3.2', mongoid: '7.2.3', mongodb: '4.4', json: '~> 2.0' } + - { ruby: '3.2', mongoid: '7.3.0', mongodb: '4.4', json: '~> 2.0' } + - { ruby: '3.3', mongoid: '7.3.0', mongodb: '5.0', json: '~> 2.0' } + - { ruby: '3.4', mongoid: '7.5.2', mongodb: '5.0', json: '~> 2.0' } + - { ruby: '3.4', mongoid: '8.1.11', mongodb: '6.0', json: '~> 2.0' } + - { ruby: '3.4', mongoid: '8.1.11', mongodb: '7.0', json: '~> 2.0' } + - { ruby: '3.4', mongoid: '8.1.11', mongodb: '8.0', json: '~> 2.0' } + - { ruby: '3.3', mongoid: '~> 9.1', mongodb: '8.0', grape: '~> 3.2', json: '~> 3.0' } + - { ruby: '3.4', mongoid: '~> 9.1', mongodb: '8.0', grape: '~> 4.0', json: '~> 3.0' } + - { ruby: '4.0', mongoid: '~> 9.1', mongodb: '8.0', grape: '~> 4.0', json: '~> 3.0' } + name: test (ruby=${{ matrix.entry.ruby }}, mongoid=${{ matrix.entry.mongoid }}, mongodb=${{ matrix.entry.mongodb }}, grape=${{ matrix.entry.grape || 'auto' }}, json=${{ matrix.entry.json }}) + services: + mongodb: + image: mongo:${{ matrix.entry.mongodb }} + ports: + - 27017:27017 + options: >- + --health-cmd "mongosh --quiet --eval 'db.adminCommand({ ping: 1 })' || mongo --quiet --eval 'db.adminCommand({ ping: 1 })'" + --health-interval 10s + --health-timeout 5s + --health-retries 10 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v7 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.entry.ruby }} - - uses: browser-actions/setup-geckodriver@latest + bundler-cache: true + cache-version: mongoid-${{ matrix.entry.mongoid }}-grape-${{ matrix.entry.grape || 'auto' }}-json-${{ matrix.entry.json }} + - uses: browser-actions/setup-firefox@v1.7.2 with: - token: ${{ secrets.GITHUB_TOKEN }} - - run: geckodriver --version - - uses: supercharge/mongodb-github-action@1.7.0 - with: - mongodb-version: ${{ matrix.entry.mongodb }} + firefox-version: latest - name: Test - uses: GabrielBB/xvfb-action@v1 - env: - DATABASE_ADAPTER: mongoid - MONGOID_VERSION: ${{ matrix.entry.mongoid }} - with: - run: | - bundle install - bundle exec rake spec + run: xvfb-run --auto-servernum bundle exec rake spec diff --git a/.github/workflows/test-postgresql.yml b/.github/workflows/test-postgresql.yml index 00ab85f..fbaa6b8 100644 --- a/.github/workflows/test-postgresql.yml +++ b/.github/workflows/test-postgresql.yml @@ -1,43 +1,61 @@ --- -name: test +name: PostgreSQL on: [push, pull_request] +permissions: + contents: read +concurrency: + group: postgresql-${{ github.ref }} + cancel-in-progress: true jobs: test: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 + timeout-minutes: 20 + env: + DATABASE_ADAPTER: activerecord + ACTIVERECORD_VERSION: ${{ matrix.entry.active_record }} + GRAPE_VERSION: ${{ matrix.entry.grape || '>= 0' }} + JSON_VERSION: ${{ matrix.entry.json }} + DATABASE_URL: postgres://test:password@localhost/slack_ruby_bot_server_test strategy: + fail-fast: false matrix: entry: - - { ruby: 3.2, postgresql: 11, active_record: '~> 6.0.0' } - - { ruby: 3.2, postgresql: 11, active_record: '~> 6.1.0' } - - { ruby: 3.2, postgresql: 14, active_record: '~> 7.0.0' } - - { ruby: 3.3, postgresql: 14, active_record: '~> 7.2.0' } - - { ruby: 3.4, postgresql: 14, active_record: '~> 7.2.0' } - - { ruby: 3.4, postgresql: 15, active_record: '~> 8.0.2' } - - { ruby: 3.4, postgresql: 16, active_record: '~> 8.0.2' } - - { ruby: 3.4, postgresql: 17, active_record: '~> 8.0.2' } - name: test (ruby=${{ matrix.entry.ruby }}, postgresql=${{ matrix.entry.postgresql }}, active_record=${{ matrix.entry.active_record }}) + # Active Support 6.x, 7.0, and 8.0 pass quirks_mode, removed in JSON 3. + - { ruby: '3.2', postgresql: '11', active_record: '~> 6.0.0', json: '~> 2.0' } + - { ruby: '3.2', postgresql: '11', active_record: '~> 6.1.0', json: '~> 2.0' } + - { ruby: '3.2', postgresql: '14', active_record: '~> 7.0.0', json: '~> 2.0' } + - { ruby: '3.3', postgresql: '14', active_record: '~> 7.2.0', json: '~> 3.0' } + - { ruby: '3.4', postgresql: '14', active_record: '~> 7.2.0', json: '~> 3.0' } + - { ruby: '3.4', postgresql: '15', active_record: '~> 8.0.2', json: '~> 2.0' } + - { ruby: '3.4', postgresql: '16', active_record: '~> 8.0.2', json: '~> 2.0' } + - { ruby: '3.4', postgresql: '17', active_record: '~> 8.0.2', json: '~> 2.0' } + - { ruby: '3.3', postgresql: '18', active_record: '~> 8.1', grape: '~> 3.2', json: '~> 3.0' } + - { ruby: '3.4', postgresql: '18', active_record: '~> 8.1', grape: '~> 4.0', json: '~> 3.0' } + - { ruby: '4.0', postgresql: '18', active_record: '~> 8.1', grape: '~> 4.0', json: '~> 3.0' } + name: test (ruby=${{ matrix.entry.ruby }}, postgresql=${{ matrix.entry.postgresql }}, active_record=${{ matrix.entry.active_record }}, grape=${{ matrix.entry.grape || 'auto' }}, json=${{ matrix.entry.json }}) + services: + postgresql: + image: postgres:${{ matrix.entry.postgresql }} + env: + POSTGRES_DB: slack_ruby_bot_server_test + POSTGRES_USER: test + POSTGRES_PASSWORD: password + ports: + - 5432:5432 + options: >- + --health-cmd "pg_isready -U test -d slack_ruby_bot_server_test" + --health-interval 10s + --health-timeout 5s + --health-retries 10 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v7 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.entry.ruby }} - - uses: browser-actions/setup-geckodriver@latest - with: - token: ${{ secrets.GITHUB_TOKEN }} - - run: geckodriver --version - - uses: harmon758/postgresql-action@v1 + bundler-cache: true + cache-version: activerecord-${{ matrix.entry.active_record }}-grape-${{ matrix.entry.grape || 'auto' }}-json-${{ matrix.entry.json }} + - uses: browser-actions/setup-firefox@v1.7.2 with: - postgresql version: ${{ matrix.entry.postgresql }} - postgresql db: slack_ruby_bot_server_test - postgresql user: test - postgresql password: password + firefox-version: latest - name: Test - uses: GabrielBB/xvfb-action@v1 - env: - DATABASE_ADAPTER: activerecord - ACTIVERECORD_VERSION: ${{ matrix.entry.active_record }} - DATABASE_URL: postgres://test:password@localhost/slack_ruby_bot_server_test - with: - run: | - bundle install - bundle exec rake spec + run: xvfb-run --auto-servernum bundle exec rake spec diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..9a9273a --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,55 @@ +# Agent Instructions + +## Project + +Slack Ruby Bot Server is a Ruby gem providing a Grape API and multi-team Slack bot lifecycle management. Read [CONTRIBUTING.md](CONTRIBUTING.md) before making changes. + +- `lib/slack-ruby-bot-server/api/`: endpoints, presenters, helpers, and middleware. +- `lib/slack-ruby-bot-server/models/` and `lib/slack-ruby-bot-server/ext/`: models and database-specific behavior. +- `lib/slack-ruby-bot-server/app.rb` and `service.rb`: application setup and service lifecycle. +- `public/`: registration UI and static assets. +- `spec/`: RSpec API, model, service, and browser integration tests. +- `spec/database_adapters/`: adapter setup, test schemas, and database cleanup. +- `.github/workflows/`: compatibility matrices, lint, and Danger. + +## Dependencies and Tests + +Set the adapter and version selectors before installing dependencies. Use a Ruby version covered by CI; the examples below match the current Ruby 4/Grape 4 jobs. + +Start MongoDB locally, then run: + +```sh +export DATABASE_ADAPTER=mongoid MONGOID_VERSION='~> 9.1' GRAPE_VERSION='~> 4.0' JSON_VERSION='~> 3.0' +export DATABASE_URL=mongodb://localhost/slack_ruby_bot_server_test +bundle install +bundle exec rake +``` + +For PostgreSQL, start the server and adjust local credentials in `DATABASE_URL` as needed: + +```sh +export DATABASE_ADAPTER=activerecord ACTIVERECORD_VERSION='~> 8.1' GRAPE_VERSION='~> 4.0' JSON_VERSION='~> 3.0' +export DATABASE_URL=postgresql://localhost/slack_ruby_bot_server_test +bundle install +bundle exec rake +``` + +Use only a dedicated local test database. The suite deletes or truncates records, and the Active Record schema recreates the teams table. + +`bundle exec rake` runs RuboCop and the full RSpec suite. Use `bundle exec rspec path/to/spec.rb` for focused checks. Validate shared behavior against both adapters before committing; batch final checks rather than repeatedly running full suites during editing. + +Browser tests require Firefox. Selenium Manager provisions geckodriver when needed. On Linux, install Xvfb and run `xvfb-run --auto-servernum bundle exec rake`. Adapter suites must run sequentially on one host because Capybara uses port 9293. + +`Gemfile.lock` is intentionally ignored. Re-resolve dependencies when switching adapter/version combinations; do not add the lockfile to the gem repository. + +## Changes and Compatibility + +Preserve both database adapters and the existing unique CI matrix combinations, including explicit Grape 3/4 coverage. Quote version strings in workflow YAML and validate workflow changes with `actionlint`. + +Keep JSON 2 constraints scoped to affected legacy matrix jobs through `JSON_VERSION`; modern jobs explicitly test JSON 3. Do not add a blanket JSON upper bound to the Gemfile or gemspec. + +Follow existing Ruby conventions and `.rubocop.yml`; do not relax cops or add exclusions just to make checks pass. Add focused regression tests for changed behavior and update directly related documentation. Keep Markdown paragraphs on one line. + +Add a changelog entry under the next release, with contributor attribution and the linked PR number once available. Danger checks the changelog and table of contents. + +Keep each PR branch to one logical commit. Include `Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>` for Copilot contributions, and identify AI-authored GitHub descriptions/comments as posted on behalf of the requesting user. Never force-push `master`. Do not publish a release or push a release tag without explicit authorization. diff --git a/CHANGELOG.md b/CHANGELOG.md index e1c39b6..fdc1c70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### 2.2.1 (Next) * Your contribution here. +* [#182](https://github.com/slack-ruby/slack-ruby-bot-server/pull/182): Modernize CI with current actions, native database services, dependency caching, and explicit Ruby 4, Mongoid 9, Active Record 8.1, PostgreSQL 18, Grape 3/4, and JSON 2/3 coverage while retaining existing compatibility combinations - [@dblock](https://github.com/dblock), [@Copilot](https://github.com/apps/copilot-swe-agent). * [#181](https://github.com/slack-ruby/slack-ruby-bot-server/pull/181): Migrated Danger to `danger-pr-comment` workflow - [@dblock](https://github.com/dblock). * [#180](https://github.com/slack-ruby/slack-ruby-bot-server/pull/180): Add support for Grape v3 - [@markokajzer](https://github.com/markokajzer). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 60b7c36..b49c768 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,15 +28,20 @@ git checkout -b my-feature-branch ## Bundle Install and Test -Ensure that you can build the project and run tests. +Select a database adapter before installing dependencies. For example, to match the Ruby 4 MongoDB/Grape 4 job, start MongoDB locally and run: -``` +```sh +export DATABASE_ADAPTER=mongoid MONGOID_VERSION='~> 9.1' GRAPE_VERSION='~> 4.0' JSON_VERSION='~> 3.0' +export DATABASE_URL=mongodb://localhost/slack_ruby_bot_server_test bundle install bundle exec rake ``` -If you are a linux user, you will need to install [geckodriver](https://github.com/mozilla/geckodriver/releases), [firefox](https://download-installer.cdn.mozilla.net/pub/firefox/releases/), and `Xvfb`. -And, to get a display for the headless browser, run `Xvfb` manually. You can see more explanations [here](http://elementalselenium.com/tips/38-headless). +For PostgreSQL, set `DATABASE_ADAPTER=activerecord`, `ACTIVERECORD_VERSION`, and `DATABASE_URL` instead. `GRAPE_VERSION` optionally constrains the Grape version for either adapter; without it, Bundler selects a compatible version. CI preserves the older compatibility combinations and adds explicit Grape 3 and 4 jobs with current ORM versions, including Ruby 4. Each database runs in a health-checked service container, while Firefox runs under Xvfb. Selenium Manager provisions the matching driver. + +`JSON_VERSION` optionally constrains the development/test JSON dependency; otherwise it is unrestricted. CI uses JSON 2 for Mongoid 6-8 and Active Record 6.x, 7.0, and 8.0 because their Active Support versions pass `quirks_mode`, removed in JSON 3. Mongoid 9, Active Record 7.2, and Active Record 8.1 jobs explicitly test JSON 3. These selectors do not restrict the published gem's runtime dependencies. + +On Linux, install [Firefox](https://www.mozilla.org/firefox/) and `Xvfb`, then run: ``` xvfb-run --auto-servernum bundle exec rake @@ -121,7 +126,7 @@ git push origin my-feature-branch -f ## Check on Your Pull Request -Go back to your pull request after a few minutes and see whether it passed muster with Travis-CI. Everything should look green, otherwise fix issues and amend your commit as described above. +Go back to your pull request after a few minutes and check its GitHub Actions results. Everything should look green, otherwise fix issues and update your commit as described above. ## Be Patient diff --git a/Gemfile b/Gemfile index 04e0a2c..0c7ff3e 100644 --- a/Gemfile +++ b/Gemfile @@ -31,7 +31,9 @@ gem 'fabrication' gem 'faker' gem 'faraday' gem 'faraday-rack' +gem 'grape', ENV.fetch('GRAPE_VERSION', '>= 0') gem 'hyperclient' +gem 'json', ENV.fetch('JSON_VERSION', '>= 0') gem 'mutex_m' gem 'rack' gem 'rack-server-pages' diff --git a/spec/support/api/endpoints/it_behaves_like_a_cursor_api.rb b/spec/support/api/endpoints/it_behaves_like_a_cursor_api.rb index 1f77202..e8762dd 100644 --- a/spec/support/api/endpoints/it_behaves_like_a_cursor_api.rb +++ b/spec/support/api/endpoints/it_behaves_like_a_cursor_api.rb @@ -31,7 +31,7 @@ models_ids.concat(response.map { |instance| instance._links.self._url.gsub("http://example.org/api/#{model_ps}/", '') }) break unless response._links[:next] - next_cursor = CGI.parse(URI.parse(response._links.next._url).query).map { |a| [a[0], a[1][0]] }.to_h + next_cursor = URI.decode_www_form(URI.parse(response._links.next._url).query).to_h end expect(models_ids.uniq.count).to eq model.all.count end @@ -44,7 +44,7 @@ models_ids.concat(response.map { |instance| instance._links.self._url.gsub("http://example.org/api/#{model_ps}/", '') }) break unless response._links[:next] - next_cursor = CGI.parse(URI.parse(response._links.next._url).query).map { |a| [a[0], a[1][0]] }.to_h + next_cursor = URI.decode_www_form(URI.parse(response._links.next._url).query).to_h end expect(models_ids.uniq.count).to eq model.all.count - 3 end