diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 12c0dbe..a498ae5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,6 +36,16 @@ jobs: MYSQL_PASSWORD: with_advisory_pass MYSQL_DATABASE: with_advisory_lock_test MYSQL_ROOT_HOST: '%' + mariadb: + image: mariadb:12 + ports: + - 3306 + env: + MARIADB_ROOT_PASSWORD: root + MARIADB_DATABASE: with_advisory_lock_trilogy_test + MARIADB_USER: with_advisory + MARIADB_PASSWORD: with_advisory_pass + MARIADB_ROOT_HOST: '%' strategy: fail-fast: false matrix: @@ -60,10 +70,13 @@ jobs: bundler-cache: true rubygems: latest + - name: Setup test databases + timeout-minutes: 5 env: DATABASE_URL_PG: postgres://with_advisory:with_advisory_pass@localhost:${{ job.services.postgres.ports[5432] }}/with_advisory_lock_test DATABASE_URL_MYSQL: mysql2://with_advisory:with_advisory_pass@127.0.0.1:${{ job.services.mysql.ports[3306] }}/with_advisory_lock_test + DATABASE_URL_TRILOGY: trilogy://with_advisory:with_advisory_pass@127.0.0.1:${{ job.services.mariadb.ports[3306] }}/with_advisory_lock_trilogy_test run: | cd test/dummy bundle exec rake db:test:prepare @@ -72,5 +85,6 @@ jobs: env: DATABASE_URL_PG: postgres://with_advisory:with_advisory_pass@localhost:${{ job.services.postgres.ports[5432] }}/with_advisory_lock_test DATABASE_URL_MYSQL: mysql2://with_advisory:with_advisory_pass@127.0.0.1:${{ job.services.mysql.ports[3306] }}/with_advisory_lock_test + DATABASE_URL_TRILOGY: trilogy://with_advisory:with_advisory_pass@127.0.0.1:${{ job.services.mariadb.ports[3306] }}/with_advisory_lock_trilogy_test WITH_ADVISORY_LOCK_PREFIX: ${{ github.run_id }} run: bin/rails test diff --git a/test/dummy/app/models/trilogy_label.rb b/test/dummy/app/models/trilogy_label.rb new file mode 100644 index 0000000..34076b0 --- /dev/null +++ b/test/dummy/app/models/trilogy_label.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class TrilogyLabel < TrilogyRecord + self.table_name = 'trilogy_labels' +end \ No newline at end of file diff --git a/test/dummy/app/models/trilogy_record.rb b/test/dummy/app/models/trilogy_record.rb new file mode 100644 index 0000000..f4f96f5 --- /dev/null +++ b/test/dummy/app/models/trilogy_record.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class TrilogyRecord < ActiveRecord::Base + self.abstract_class = true + establish_connection :trilogy +end \ No newline at end of file diff --git a/test/dummy/app/models/trilogy_tag.rb b/test/dummy/app/models/trilogy_tag.rb new file mode 100644 index 0000000..da5ce75 --- /dev/null +++ b/test/dummy/app/models/trilogy_tag.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class TrilogyTag < TrilogyRecord + self.table_name = 'trilogy_tags' + + after_save do + TrilogyTagAudit.create(tag_name: name) + TrilogyLabel.create(name: name) + end +end \ No newline at end of file diff --git a/test/dummy/app/models/trilogy_tag_audit.rb b/test/dummy/app/models/trilogy_tag_audit.rb new file mode 100644 index 0000000..6da9e9f --- /dev/null +++ b/test/dummy/app/models/trilogy_tag_audit.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class TrilogyTagAudit < TrilogyRecord + self.table_name = 'trilogy_tag_audits' +end \ No newline at end of file diff --git a/test/dummy/config/database.yml b/test/dummy/config/database.yml index 8c73631..efd2e36 100644 --- a/test/dummy/config/database.yml +++ b/test/dummy/config/database.yml @@ -11,3 +11,9 @@ test: url: "<%= ENV['DATABASE_URL_MYSQL'] %>" properties: allowPublicKeyRetrieval: true + trilogy: + <<: *default + url: "<%= ENV['DATABASE_URL_TRILOGY'] %>" + adapter: trilogy + properties: + allowPublicKeyRetrieval: true diff --git a/test/dummy/db/trilogy_schema.rb b/test/dummy/db/trilogy_schema.rb new file mode 100644 index 0000000..fbfa1e7 --- /dev/null +++ b/test/dummy/db/trilogy_schema.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +ActiveRecord::Schema.define(version: 1) do + create_table 'trilogy_tags', force: true do |t| + t.string 'name' + end + + create_table 'trilogy_tag_audits', id: false, force: true do |t| + t.string 'tag_name' + end + + create_table 'trilogy_labels', id: false, force: true do |t| + t.string 'name' + end +end \ No newline at end of file diff --git a/test/dummy/lib/tasks/trilogy.rake b/test/dummy/lib/tasks/trilogy.rake new file mode 100644 index 0000000..a1ef741 --- /dev/null +++ b/test/dummy/lib/tasks/trilogy.rake @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +namespace :db do + namespace :trilogy do + desc 'Create and load the Trilogy database schema' + task prepare: :environment do + ActiveRecord::Base.establish_connection(:trilogy) + + # Load the schema + load Rails.root.join('db', 'trilogy_schema.rb') + + puts 'Trilogy database schema loaded successfully' + rescue StandardError => e + puts "Error loading Trilogy schema: #{e.message}" + raise e + end + end + + namespace :test do + task prepare: :environment do + # Setup PostgreSQL database + ActiveRecord::Base.establish_connection(:primary) + load Rails.root.join('db', 'schema.rb') + + # Setup MySQL database + ActiveRecord::Base.establish_connection(:secondary) + load Rails.root.join('db', 'secondary_schema.rb') + + # Setup Trilogy database + ActiveRecord::Base.establish_connection(:trilogy) + load Rails.root.join('db', 'trilogy_schema.rb') + + puts 'All test databases prepared successfully' + end + end +end \ No newline at end of file diff --git a/test/sanity_check_test.rb b/test/sanity_check_test.rb index 4917fed..46e52a1 100644 --- a/test/sanity_check_test.rb +++ b/test/sanity_check_test.rb @@ -3,7 +3,7 @@ require 'test_helper' class SanityCheckTest < GemTestCase - test 'PostgreSQL and MySQL databases are properly isolated' do + test 'PostgreSQL, MySQL, and Trilogy databases are properly isolated' do # Create a tag in PostgreSQL database pg_tag = Tag.create!(name: 'postgresql-only-tag') @@ -26,9 +26,31 @@ class SanityCheckTest < GemTestCase assert_not Tag.exists?(name: 'mysql-only-tag') assert_equal 0, Tag.where(name: 'mysql-only-tag').count + # Create a tag in Trilogy database + trilogy_tag = TrilogyTag.create!(name: 'trilogy-only-tag') + + # Verify it exists in Trilogy + assert TrilogyTag.exists?(name: 'trilogy-only-tag') + assert_equal 1, TrilogyTag.where(name: 'trilogy-only-tag').count + + # Verify it does NOT exist in PostgreSQL or MySQL databases + assert_not Tag.exists?(name: 'trilogy-only-tag') + assert_equal 0, Tag.where(name: 'trilogy-only-tag').count + assert_not MysqlTag.exists?(name: 'trilogy-only-tag') + assert_equal 0, MysqlTag.where(name: 'trilogy-only-tag').count + + # Verify PostgreSQL tag does NOT exist in Trilogy + assert_not TrilogyTag.exists?(name: 'postgresql-only-tag') + assert_equal 0, TrilogyTag.where(name: 'postgresql-only-tag').count + + # Verify MySQL tag does NOT exist in Trilogy + assert_not TrilogyTag.exists?(name: 'mysql-only-tag') + assert_equal 0, TrilogyTag.where(name: 'mysql-only-tag').count + # Clean up pg_tag.destroy mysql_tag.destroy + trilogy_tag.destroy end test 'PostgreSQL models use PostgreSQL adapter' do @@ -43,21 +65,31 @@ class SanityCheckTest < GemTestCase assert_equal 'Mysql2', MysqlLabel.connection.adapter_name end - test 'can write to both databases in same test' do - # Create records in both databases + test 'Trilogy models use Trilogy adapter' do + assert_equal 'Trilogy', TrilogyTag.connection.adapter_name + assert_equal 'Trilogy', TrilogyTagAudit.connection.adapter_name + assert_equal 'Trilogy', TrilogyLabel.connection.adapter_name + end + + test 'can write to all three databases in same test' do + # Create records in all three databases pg_tag = Tag.create!(name: 'test-pg') mysql_tag = MysqlTag.create!(name: 'test-mysql') + trilogy_tag = TrilogyTag.create!(name: 'test-trilogy') - # Both should have IDs + # All should have IDs assert pg_tag.persisted? assert mysql_tag.persisted? + assert trilogy_tag.persisted? - # IDs should be independent (both could be 1 if tables are empty) + # IDs should be independent (all could be 1 if tables are empty) assert_kind_of Integer, pg_tag.id assert_kind_of Integer, mysql_tag.id + assert_kind_of Integer, trilogy_tag.id # Clean up pg_tag.destroy mysql_tag.destroy + trilogy_tag.destroy end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 09a6358..d3691d4 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -20,7 +20,7 @@ class GemTestCase < ActiveSupport::TestCase def self.startup # Validate environment variables when tests actually start running - %w[DATABASE_URL_PG DATABASE_URL_MYSQL].each do |var| + %w[DATABASE_URL_PG DATABASE_URL_MYSQL DATABASE_URL_TRILOGY].each do |var| abort "Missing required environment variable: #{var}" if ENV[var].nil? || ENV[var].empty? end end diff --git a/test/with_advisory_lock/multi_adapter_test.rb b/test/with_advisory_lock/multi_adapter_test.rb index 1ac1083..a06837a 100644 --- a/test/with_advisory_lock/multi_adapter_test.rb +++ b/test/with_advisory_lock/multi_adapter_test.rb @@ -3,15 +3,37 @@ require 'test_helper' class MultiAdapterIsolationTest < GemTestCase - test 'postgresql and mysql adapters do not overlap' do + test 'postgresql, mysql, and trilogy adapters do not overlap' do lock_name = 'multi-adapter-lock' + # PostgreSQL lock doesn't block MySQL Tag.with_advisory_lock(lock_name) do assert MysqlTag.with_advisory_lock(lock_name, timeout_seconds: 0) { true } end + # MySQL lock doesn't block PostgreSQL MysqlTag.with_advisory_lock(lock_name) do assert Tag.with_advisory_lock(lock_name, timeout_seconds: 0) { true } end + + # PostgreSQL lock doesn't block Trilogy + Tag.with_advisory_lock(lock_name) do + assert TrilogyTag.with_advisory_lock(lock_name, timeout_seconds: 0) { true } + end + + # Trilogy lock doesn't block PostgreSQL + TrilogyTag.with_advisory_lock(lock_name) do + assert Tag.with_advisory_lock(lock_name, timeout_seconds: 0) { true } + end + + # MySQL lock doesn't block Trilogy + MysqlTag.with_advisory_lock(lock_name) do + assert TrilogyTag.with_advisory_lock(lock_name, timeout_seconds: 0) { true } + end + + # Trilogy lock doesn't block MySQL + TrilogyTag.with_advisory_lock(lock_name) do + assert MysqlTag.with_advisory_lock(lock_name, timeout_seconds: 0) { true } + end end end diff --git a/test/with_advisory_lock/parallelism_test.rb b/test/with_advisory_lock/parallelism_test.rb index 06620c7..ab42f2e 100644 --- a/test/with_advisory_lock/parallelism_test.rb +++ b/test/with_advisory_lock/parallelism_test.rb @@ -99,3 +99,11 @@ def model_class MysqlTag end end + +class TrilogyParallelismTest < GemTestCase + include ParallelismTestCases + + def model_class + TrilogyTag + end +end diff --git a/test/with_advisory_lock/shared_test.rb b/test/with_advisory_lock/shared_test.rb index eb381a3..5b529c6 100644 --- a/test/with_advisory_lock/shared_test.rb +++ b/test/with_advisory_lock/shared_test.rb @@ -127,3 +127,29 @@ class MySQLSharedLocksTest < GemTestCase assert_match(/shared locks are not supported/, exception.message) end end + +class TrilogySharedLocksTest < GemTestCase + self.use_transactional_tests = false + + test 'does not allow two exclusive locks' do + one = SharedTestWorker.new(TrilogyTag, false) + assert_predicate(one, :locked?) + + two = SharedTestWorker.new(TrilogyTag, false) + refute(two.locked?) + + one.cleanup! + two.cleanup! + end + + test 'raises an error when attempting to use a shared lock' do + one = SharedTestWorker.new(TrilogyTag, true) + assert_equal(false, one.locked?) + + exception = assert_raises(ArgumentError) do + one.cleanup! + end + + assert_match(/shared locks are not supported/, exception.message) + end +end diff --git a/test/with_advisory_lock/thread_test.rb b/test/with_advisory_lock/thread_test.rb index bfc00d7..8760850 100644 --- a/test/with_advisory_lock/thread_test.rb +++ b/test/with_advisory_lock/thread_test.rb @@ -81,3 +81,11 @@ def model_class MysqlTag end end + +class TrilogyThreadTest < GemTestCase + include ThreadTestCases + + def model_class + TrilogyTag + end +end