diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index d986903..3d75f24 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -8,30 +8,121 @@ on: jobs: test: + name: "Ruby ${{ matrix.ruby-version }}${{ matrix.rails-version && format(' / Rails {0}', matrix.rails-version) || '' }} / ${{ matrix.adapter-name }}${{ matrix.adapter-version && format(' {0}', matrix.adapter-version) || '' }}" runs-on: ubuntu-latest strategy: fail-fast: false matrix: - ruby-version: ["3.2", "3.3", "3.4", "4.0"] - - services: - redis: - image: redis:7 - ports: - - 6379:6379 - options: >- - --health-cmd "redis-cli ping" - --health-interval 10s - --health-timeout 5s - --health-retries 5 + include: + - ruby-version: "3.2" + adapter: redis + adapter-name: Redis + - ruby-version: "3.2" + adapter: solid/sqlite3 + adapter-name: SQLite + - ruby-version: "3.3" + adapter: redis + adapter-name: Redis + - ruby-version: "3.3" + adapter: solid/sqlite3 + adapter-name: SQLite + - ruby-version: "3.4" + adapter: redis + adapter-name: Redis + - ruby-version: "3.4" + adapter: solid/sqlite3 + adapter-name: SQLite + + # Run tests against all adapters and 2 latest rails majors for latest Ruby version + - ruby-version: "4.0" + adapter: redis + adapter-name: Redis + - ruby-version: "4.0" + adapter: redis + adapter-name: Redis + rails-version: "7" + - ruby-version: "4.0" + adapter: solid/sqlite3 + adapter-name: SQLite + - ruby-version: "4.0" + adapter: solid/sqlite3 + adapter-name: SQLite + adapter-version: "1.7" + rails-version: "7" + - ruby-version: "4.0" + adapter: solid/postgresql + adapter-name: PostgreSQL + - ruby-version: "4.0" + adapter: solid/postgresql + adapter-name: PostgreSQL + rails-version: "7" + - ruby-version: "4.0" + adapter: solid/mysql2 + adapter-name: MySQL (mysql2) + - ruby-version: "4.0" + adapter: solid/mysql2 + adapter-name: MySQL (mysql2) + rails-version: "7" + - ruby-version: "4.0" + adapter: solid/trilogy + adapter-name: "MySQL (trilogy)" + - ruby-version: "4.0" + adapter: solid/trilogy + adapter-name: "MySQL (trilogy)" + rails-version: "7" env: + FINO_TEST_ADAPTER: ${{ matrix.adapter }} FINO_TEST_REDIS_HOST: localhost + FINO_TEST_POSTGRES_HOST: localhost + FINO_TEST_POSTGRES_USER: postgres + FINO_TEST_POSTGRES_PASSWORD: postgres + FINO_TEST_POSTGRES_DB: fino_test + FINO_TEST_MYSQL_HOST: 127.0.0.1 + FINO_TEST_MYSQL_USER: root + FINO_TEST_MYSQL_PASSWORD: root + FINO_TEST_MYSQL_DB: fino_test steps: - uses: actions/checkout@v4 + - name: Set adapter version override + if: matrix.adapter-version + run: | + GEM_NAME="${FINO_TEST_ADAPTER#solid/}" + ENV_NAME="$(echo "${GEM_NAME}" | tr '[:lower:]' '[:upper:]')_VERSION" + echo "${ENV_NAME}=${{ matrix.adapter-version }}" >> $GITHUB_ENV + + - name: Set Rails version override + if: matrix.rails-version + run: echo "RAILS_VERSION=${{ matrix.rails-version }}" >> $GITHUB_ENV + + - name: Start Redis + if: matrix.adapter == 'redis' + run: | + docker run -d --name redis -p 6379:6379 redis:7 + timeout 30 bash -c 'until docker exec redis redis-cli ping 2>/dev/null; do sleep 1; done' + + - name: Start PostgreSQL + if: matrix.adapter == 'solid/postgresql' + run: | + docker run -d --name postgres -p 5432:5432 \ + -e POSTGRES_USER=postgres \ + -e POSTGRES_PASSWORD=postgres \ + -e POSTGRES_DB=fino_test \ + postgres:17 + timeout 30 bash -c 'until docker exec postgres pg_isready 2>/dev/null; do sleep 1; done' + + - name: Start MySQL + if: matrix.adapter == 'solid/mysql2' || matrix.adapter == 'solid/trilogy' + run: | + docker run -d --name mysql -p 3306:3306 \ + -e MYSQL_ROOT_PASSWORD=root \ + -e MYSQL_DATABASE=fino_test \ + mysql:8.4 + timeout 60 bash -c 'until docker exec mysql mysqladmin ping -h 127.0.0.1 --silent 2>/dev/null; do sleep 1; done' + - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby-version }} diff --git a/Gemfile b/Gemfile index 505f186..2a90412 100644 --- a/Gemfile +++ b/Gemfile @@ -16,8 +16,13 @@ gem "guard-livereload" gem "rack-livereload" gem "puma" -gem "rails", "~> #{ENV['RAILS_VERSION'] || '8.0'}" -gem "sqlite3", "~> #{ENV['SQLITE_VERSION'] || '2.7'}" +gem "rails", "~> #{ENV.fetch('RAILS_VERSION', '8.0')}" + +# Database adapters +gem "mysql2", "~> #{ENV.fetch('MYSQL2_VERSION', '0')}" +gem "pg", "~> #{ENV.fetch('POSTGRESQL_VERSION', '1')}" +gem "sqlite3", "~> #{ENV.fetch('SQLITE3_VERSION', '2')}" +gem "trilogy", "~> #{ENV.fetch('TRILOGY_VERSION', '2')}" gem "rspec", "~> 3.0" gem "rubocop", "~> 1.80.1", require: false diff --git a/fino-rails.gemspec b/fino-rails.gemspec index 55e9ba5..517d51f 100644 --- a/fino-rails.gemspec +++ b/fino-rails.gemspec @@ -29,5 +29,5 @@ Gem::Specification.new do |spec| ] spec.add_dependency "fino", "~> #{Fino::VERSION}" - spec.add_dependency "rails", "~> 8.0" + spec.add_dependency "rails", ">= 7.0" end diff --git a/fino-solid.gemspec b/fino-solid.gemspec index 2a62a0b..d3daf4f 100644 --- a/fino-solid.gemspec +++ b/fino-solid.gemspec @@ -30,8 +30,4 @@ Gem::Specification.new do |spec| spec.add_dependency "activerecord", ">= 6.1" spec.add_dependency "fino", "~> #{Fino::VERSION}" - - spec.add_development_dependency "mysql2", ">= 0.5" - spec.add_development_dependency "pg", ">= 1.0" - spec.add_development_dependency "sqlite3", ">= 1.4" end diff --git a/lib/fino/solid/adapter.rb b/lib/fino/solid/adapter.rb index ad15d6d..4ad22f9 100644 --- a/lib/fino/solid/adapter.rb +++ b/lib/fino/solid/adapter.rb @@ -37,7 +37,7 @@ def write(setting_definition, value, overrides, variants) Fino::Solid::Setting.upsert( { key: setting_definition.key, data: data }, - unique_by: :key + **unique_by_option(:key) ) end @@ -45,8 +45,8 @@ def read_persisted_setting_keys Fino::Solid::Setting.pluck(:key) end - def clear(setting_key) - Fino::Solid::Setting.where(key: setting_key).delete_all > 0 + def clear(setting_key) # rubocop:disable Naming/PredicateMethod + Fino::Solid::Setting.where(key: setting_key).delete_all.positive? end def fetch_value_from(raw_adapter_data) @@ -70,7 +70,7 @@ def record_ab_testing_conversion(setting_definition, variant, scope, time) scope: scope.to_s, converted_at: time }, - unique_by: :idx_fino_conversions_unique + **unique_by_option(:idx_fino_conversions_unique) ) end @@ -101,6 +101,18 @@ def fetch_raw_variants_from(raw_adapter_data) memo << { percentage: percentage.to_f, value: value } end end + + private + + def unique_by_option(constraint) + return {} if mysql_adapter? + + { unique_by: constraint } + end + + def mysql_adapter? + Fino::Solid::Record.connection.adapter_name.match?(/Mysql2|Trilogy/i) + end end end end diff --git a/spec/dummy/.gitignore b/spec/dummy/.gitignore index 3e5ce68..305ce81 100644 --- a/spec/dummy/.gitignore +++ b/spec/dummy/.gitignore @@ -6,3 +6,5 @@ /storage/* !/storage/.keep + +Gemfile.lock diff --git a/spec/integration/adapters/redis_adapter_spec.rb b/spec/integration/adapters/redis_adapter_spec.rb index 5608838..2666116 100644 --- a/spec/integration/adapters/redis_adapter_spec.rb +++ b/spec/integration/adapters/redis_adapter_spec.rb @@ -4,6 +4,8 @@ RSpec.describe "Redis adapter integration", type: :integration do before(:all) do + skip "Redis adapter not under test" unless TestHelpers.redis_adapter? + Fino.reconfigure do adapter { TestHelpers.adapter } cache { TestHelpers.cache } diff --git a/spec/integration/adapters/solid_adapter_spec.rb b/spec/integration/adapters/solid_adapter_spec.rb index cad1568..1da31be 100644 --- a/spec/integration/adapters/solid_adapter_spec.rb +++ b/spec/integration/adapters/solid_adapter_spec.rb @@ -4,10 +4,10 @@ RSpec.describe "Solid adapter integration", type: :integration do before(:all) do - SolidTestHelpers.setup_database(:sqlite3) + skip "Solid adapter not under test" unless TestHelpers.solid_adapter? Fino.reconfigure do - adapter { SolidTestHelpers.solid_adapter } + adapter { Fino::Solid::Adapter.new } cache { TestHelpers.cache } settings do @@ -38,16 +38,11 @@ end end - before do - TestHelpers.cache.clear - SolidTestHelpers.clear_database - end - it_behaves_like "fino adapter integration" describe "#supports_ab_testing_analysis?" do it "returns true" do - expect(SolidTestHelpers.solid_adapter.supports_ab_testing_analysis?).to eq(true) + expect(Fino::Solid::Adapter.new.supports_ab_testing_analysis?).to eq(true) end end end diff --git a/spec/support/from_scratch.rb b/spec/support/from_scratch.rb index 2309828..c331d2f 100644 --- a/spec/support/from_scratch.rb +++ b/spec/support/from_scratch.rb @@ -1,8 +1,20 @@ # frozen_string_literal: true RSpec.configure do |config| + config.before(:suite) do + next unless TestHelpers.solid_adapter? + + db_adapter = TestHelpers.adapter_env.delete_prefix("solid/").to_sym + SolidTestHelpers.setup_database(db_adapter) + end + config.before do TestHelpers.cache.clear - TestHelpers.redis.flushdb + + if TestHelpers.solid_adapter? + SolidTestHelpers.clear_database + else + TestHelpers.redis.flushdb + end end end diff --git a/spec/support/solid_test_helpers.rb b/spec/support/solid_test_helpers.rb index 499ae7e..99fbb43 100644 --- a/spec/support/solid_test_helpers.rb +++ b/spec/support/solid_test_helpers.rb @@ -31,6 +31,14 @@ def database_config(adapter_name) username: ENV.fetch("FINO_TEST_POSTGRES_USER", "postgres"), password: ENV.fetch("FINO_TEST_POSTGRES_PASSWORD", "") } + when :trilogy + { + adapter: "trilogy", + host: ENV.fetch("FINO_TEST_MYSQL_HOST", "mysql.fino.orb.local"), + database: ENV.fetch("FINO_TEST_MYSQL_DB", "fino_test"), + username: ENV.fetch("FINO_TEST_MYSQL_USER", "root"), + password: ENV.fetch("FINO_TEST_MYSQL_PASSWORD", "") + } when :mysql2 { adapter: "mysql2", diff --git a/spec/support/test_helpers.rb b/spec/support/test_helpers.rb index c52d6c3..cbead89 100644 --- a/spec/support/test_helpers.rb +++ b/spec/support/test_helpers.rb @@ -1,8 +1,27 @@ # frozen_string_literal: true module TestHelpers + VALID_ADAPTERS = %w[redis solid/sqlite3 solid/postgresql solid/mysql2 solid/trilogy].freeze + DEFAULT_ADAPTER = "redis" + module_function + def adapter_env + @adapter_env ||= ENV.fetch("FINO_TEST_ADAPTER", DEFAULT_ADAPTER).tap do |value| + unless VALID_ADAPTERS.include?(value) + raise "Unknown test adapter: #{value}. Valid: #{VALID_ADAPTERS.join(', ')}" + end + end + end + + def redis_adapter? + adapter_env == "redis" + end + + def solid_adapter? + adapter_env.start_with?("solid/") + end + def cache @cache ||= Fino::Cache::Memory.new(expires_in: 10) end @@ -15,6 +34,10 @@ def redis end def adapter - @adapter ||= Fino::Redis::Adapter.new(redis, namespace: "fino_test") + @adapter ||= if redis_adapter? + Fino::Redis::Adapter.new(redis, namespace: "fino_test") + elsif solid_adapter? + Fino::Solid::Adapter.new + end end end