Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions .github/workflows/claude-code-review.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: Claude Code Review

on:
pull_request:
types: [opened, synchronize]

jobs:
claude-review:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: read
id-token: write

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1

- name: Run Claude Code Review
id: claude-review
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
prompt: |
REPO: ${{ github.repository }}
PR NUMBER: ${{ github.event.pull_request.number }}

Please review this pull request and provide feedback on:
- Code quality and best practices
- Potential bugs or issues
- Performance considerations
- Security concerns
- Test coverage

Use the repository's CLAUDE.md for guidance on style and conventions. Be constructive and helpful in your feedback.

Use `gh pr comment` with your Bash tool to leave your review as a comment on the PR.

claude_args: '--allowed-tools "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"'
41 changes: 41 additions & 0 deletions .github/workflows/claude.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Claude Code

on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
issues:
types: [opened, assigned]
pull_request_review:
types: [submitted]

jobs:
claude:
if: |
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: read
id-token: write
actions: read # Required for Claude to read CI results on PRs
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1

- name: Run Claude Code
id: claude
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}

# This is an optional setting that allows Claude to read CI results on PRs
additional_permissions: |
actions: read
171 changes: 171 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
name: Tests

on:
pull_request:
paths-ignore:
- "*.md"
- "LICENSE.txt"
push:
branches:
- main
paths-ignore:
- "*.md"
- "LICENSE.txt"

jobs:
# Main test suite - tests Ruby versions and Rails compatibility with SQLite
sqlite:
runs-on: ubuntu-latest

strategy:
fail-fast: false
matrix:
ruby_version: ["3.3", "3.4", "4.0"]
gemfile:
- Gemfile
- gemfiles/rails_7.1.gemfile
- gemfiles/rails_7.2.gemfile
- gemfiles/rails_8.1.gemfile

env:
RAILS_ENV: test
BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Ruby ${{ matrix.ruby_version }}
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby_version }}
bundler-cache: true

- name: Prepare database and run tests
# Exercise the real migration path in SQLite too so dummy/test schema
# drift is caught in the default matrix, not only adapter-specific jobs.
#
# Use `db:create db:migrate` rather than `db:migrate:reset`: the reset macro
# runs db:drop + db:create + db:migrate IN ONE PROCESS, and on SQLite the
# db:migrate step then writes through a stale connection to the just-dropped
# file, so nothing persists and the suite boots into "pending migrations"
# (PG/MySQL survive it because the DB server reconnects). CI runners start
# fresh, so no drop is needed.
run: bundle exec rake db:create db:migrate test

- name: Upload test results
if: failure()
uses: actions/upload-artifact@v4
with:
name: test-results-sqlite-ruby-${{ matrix.ruby_version }}-${{ matrix.gemfile }}
path: test/reports/
retention-days: 7

# PostgreSQL compatibility tests
postgres:
runs-on: ubuntu-latest

strategy:
fail-fast: false
matrix:
ruby_version: ["3.4"]

services:
postgres:
image: postgres:16
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: moderate_test
ports:
- 5432:5432
options: >-
--health-cmd="pg_isready -U postgres"
--health-interval=10s
--health-timeout=5s
--health-retries=5

env:
RAILS_ENV: test
DATABASE_URL: postgres://postgres:postgres@localhost:5432/moderate_test

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Ruby ${{ matrix.ruby_version }}
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby_version }}
bundler-cache: true

- name: Prepare database
# Use db:migrate:reset instead of db:test:prepare to avoid loading schema.rb
# which has SQLite-specific defaults that fail on PostgreSQL.
# db:migrate:reset does: db:drop, db:create, db:migrate (using migrations, not schema.rb)
run: bundle exec rake db:migrate:reset

- name: Run tests
run: bundle exec rake test

- name: Upload test results
if: failure()
uses: actions/upload-artifact@v4
with:
name: test-results-postgres-ruby-${{ matrix.ruby_version }}
path: test/reports/
retention-days: 7

# MySQL compatibility tests
mysql:
runs-on: ubuntu-latest

strategy:
fail-fast: false
matrix:
ruby_version: ["3.4"]

services:
mysql:
image: mysql:8
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: moderate_test
ports:
- 3306:3306
options: >-
--health-cmd="mysqladmin ping -h localhost"
--health-interval=10s
--health-timeout=5s
--health-retries=5

env:
RAILS_ENV: test
DATABASE_URL: mysql2://root:root@127.0.0.1:3306/moderate_test

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Ruby ${{ matrix.ruby_version }}
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby_version }}
bundler-cache: true

- name: Prepare database
# Use db:migrate:reset instead of db:test:prepare to avoid loading schema.rb
# which has SQLite-specific defaults that fail on MySQL (JSON column defaults).
# db:migrate:reset does: db:drop, db:create, db:migrate (using migrations, not schema.rb)
run: bundle exec rake db:migrate:reset

- name: Run tests
run: bundle exec rake test

- name: Upload test results
if: failure()
uses: actions/upload-artifact@v4
with:
name: test-results-mysql-ruby-${{ matrix.ruby_version }}
path: test/reports/
retention-days: 7
8 changes: 8 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
AllCops:
TargetRubyVersion: 3.2

Style/StringLiterals:
EnforcedStyle: double_quotes

Style/StringLiteralsInInterpolation:
EnforcedStyle: double_quotes
62 changes: 62 additions & 0 deletions .simplecov
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# frozen_string_literal: true

# SimpleCov configuration file (auto-loaded before test suite)
# This keeps test_helper.rb clean and follows best practices.
# Coherent with the rest of the gem ecosystem (usage_credits, pricing_plans, …).

SimpleCov.start do
# Use SimpleFormatter for terminal-only output (no HTML generation)
formatter SimpleCov::Formatter::SimpleFormatter

# Don't count the test suite itself toward coverage
add_filter "/test/"

# Don't count code that ISN'T unit-testable by this suite and would only distort
# the numbers:
# - Generators + their templates: these run via `rails generate moderate:install`
# / `moderate:views` in a real host, not in the engine's own unit suite. (The
# migration template IS exercised indirectly — the dummy migrates a copy of it —
# but the .erb itself is never loaded as Ruby here.)
# - version.rb: a single constant; nothing to cover.
# - The 0.x compatibility shims (text / text_validator / word_list): legacy
# profanity-validator code kept only so `validates :field, moderate: true` from
# 0.x still loads (see README "Upgrading from 0.x"). They are NOT part of the
# 1.0 Trust & Safety surface this suite tests, so they shouldn't pull the 1.0
# coverage number down.
add_filter "/lib/generators/"
add_filter "/lib/moderate/version.rb"
add_filter "/lib/moderate/text.rb"
add_filter "/lib/moderate/text_validator.rb"
add_filter "/lib/moderate/word_list.rb"

# Track Ruby files in the lib directory (gem source code)
track_files "lib/**/*.rb"

# Enable branch coverage for more detailed metrics
enable_coverage :branch

# Minimum coverage thresholds to prevent coverage REGRESSION. These reflect what
# the current shipped suite actually exercises (line ~86%, branch ~65%): the
# primitives — models, concerns, services, adapters, the facade, the value objects —
# are thoroughly covered; the lower branch number is driven by the engine's
# CONTROLLERS (the public DSA notice form + the BYOUI moderation concern) and the
# async ClassifyJob, whose request/job paths the unit suite doesn't drive. The
# thresholds sit just under the current floor so the gate catches a real regression
# without failing on the existing baseline; raise them as request/job coverage grows.
minimum_coverage line: 80, branch: 60

# Disambiguate parallel test runs
command_name "Job #{ENV['TEST_ENV_NUMBER']}" if ENV["TEST_ENV_NUMBER"]
end

# Print coverage summary to terminal after tests complete
SimpleCov.at_exit do
SimpleCov.result.format!
puts "\n" + "=" * 60
puts "COVERAGE SUMMARY"
puts "=" * 60
puts "Line Coverage: #{SimpleCov.result.covered_percent.round(2)}%"
branch_coverage = SimpleCov.result.coverage_statistics[:branch]&.percent&.round(2) || "N/A"
puts "Branch Coverage: #{branch_coverage}%"
puts "=" * 60
end
7 changes: 7 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# AGENTS.md

This file provides guidance to AI Agents (like OpenAI's Codex, Cursor Agent, Claude Code, etc) when working with code in this repository.

Please read the `README.md` for a full overview of the gem's API and philosophy, and the `docs/` directory (`docs/configuration.md`, `docs/notifications.md`, `docs/compliance.md`, `docs/madmin.md`, `docs/dsa-notice-form.md`) for the detailed integration guides.

This gem is part of a coherent ecosystem (`railsfast`, `goodmail`, `telegrama`, `usage_credits`, `pricing_plans`, `wallets`, `api_keys`). Match the ecosystem conventions exactly: a single `Moderate.configure do |config| … end` block, `has_*`/verb-style class macros, adapter objects + no-op-default hook procs, string class names constantized lazily, adaptive install migrations, Minitest with a `test/dummy` app, SimpleCov, and the README/docs voice.
16 changes: 16 additions & 0 deletions Appraisals
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# frozen_string_literal: true

# Test the minimum supported Rails version (matches the gemspec floor and the
# README's "Rails 7.1+ schema" claim — the adaptive migration must work here).
appraise "rails-7.1" do
gem "rails", "~> 7.1.0"
end

appraise "rails-7.2" do
gem "rails", "~> 7.2.0"
end

# Test the latest Rails version — this is the default/main Gemfile anyway.
appraise "rails-8.1" do
gem "rails", "~> 8.1.0"
end
Loading
Loading