integration tests

This commit is contained in:
Dylan Knutson
2025-07-13 03:13:47 +00:00
parent a9d315d993
commit af97998393
13 changed files with 256 additions and 181 deletions

View File

@@ -1,210 +1,170 @@
# frozen_string_literal: true
RSpec.describe ActiveRecord::AuxTable do
before(:all) do
# Set up the database schema for testing
ActiveRecord::Schema.define do
create_table :vehicles do |t|
t.string :type, null: false
t.string :name
t.timestamps
end
create_table :test_classes do |t|
t.string :name
t.timestamps
end
# Auxiliary tables for testing
create_table :test_table do |t|
t.references :test_class,
null: false,
foreign_key: {
to_table: :test_classes
}
t.string :name
t.timestamps
end
create_table :car_aux do |t|
t.references :vehicle, null: false, foreign_key: { to_table: :vehicles }
t.string :name
t.string :fuel_type
t.decimal :engine_size, precision: 3, scale: 1
t.timestamps
end
end
end
class TestClass < ActiveRecord::Base
include ActiveRecord::AuxTable
self.table_name = "test_classes"
aux_table(:test_table) { |t| t.string :name }
end
class Vehicle < ActiveRecord::Base
include ActiveRecord::AuxTable
self.table_name = "vehicles"
end
class Car < Vehicle
aux_table(:car_aux) { |t| t.string :fuel_type }
end
it "has a version number" do
expect(ActiveRecord::AuxTable::VERSION).not_to be nil
end
it "can create STI records" do
car = Car.create!(name: "Toyota Camry", type: "Car")
expect(car).to be_persisted
end
it "is a clean test environment" do
expect(Vehicle.count).to eq(0)
end
describe "module inclusion" do
let(:test_class) do
Class.new do
include ActiveRecord::AuxTable
def self.name
"TestClass"
end
end
end
it "can be included in a class" do
expect { test_class }.not_to raise_error
end
it "adds class methods to the including class" do
expect(test_class).to respond_to(:aux_table)
expect(test_class).to respond_to(:has_aux_tables?)
expect(test_class).to respond_to(:aux_table_configuration)
expect(test_class).to respond_to(:aux_table_configurations)
expect(TestClass).to respond_to(:aux_table)
expect(TestClass).to respond_to(:has_aux_tables?)
expect(TestClass).to respond_to(:aux_table_configuration)
expect(TestClass).to respond_to(:aux_table_configurations)
end
it "adds instance methods to the including class" do
instance = test_class.new
instance = TestClass.new
expect(instance).to respond_to(:aux_table_record)
expect(instance).to respond_to(:build_aux_table_record)
end
it "initializes aux_table_configurations as empty hash" do
expect(test_class.aux_table_configurations).to eq({})
end
it "returns false for has_aux_tables? when no tables configured" do
expect(test_class.has_aux_tables?).to be false
end
it "returns empty hash for aux_table_configuration when no config exists" do
expect(test_class.aux_table_configuration(:test_table)).to be nil
end
it "returns nil for specific aux_table_configuration when not configured" do
expect(test_class.aux_table_configuration(:test_table)).to be nil
end
end
describe "aux_table DSL method" do
let(:test_class) do
Class.new do
include ActiveRecord::AuxTable
def self.name
"TestClass"
end
def self.base_class
self
end
end
end
it "accepts table name and block" do
expect {
test_class.aux_table(:test_table) { |t| t.string :name }
}.not_to raise_error
end
it "returns configuration object" do
config = test_class.aux_table(:test_table) { |t| t.string :name }
it "configures and stores auxiliary table configuration" do
# Test configuration object creation and storage
config = TestClass.aux_table_configuration(:test_table)
expect(config).to be_a(ActiveRecord::AuxTable::Configuration)
expect(config.table_name).to eq(:test_table)
expect(config.block).to be_a(Proc)
end
it "stores configuration in aux_table_configurations" do
test_class.aux_table(:test_table) { |t| t.string :name }
# Test string to symbol conversion
expect(TestClass.aux_table_configurations.keys).to include(:test_table)
config = test_class.aux_table_configurations[:test_table]
expect(config).to be_a(ActiveRecord::AuxTable::Configuration)
expect(config.table_name).to eq(:test_table)
end
# Test has_aux_tables? method
expect(TestClass.has_aux_tables?).to be true
it "converts string table name to symbol" do
test_class.aux_table("test_table") { |t| t.string :name }
expect(test_class.aux_table_configurations.keys).to include(:test_table)
end
it "returns true for has_aux_tables? after configuration" do
test_class.aux_table(:test_table) { |t| t.string :name }
expect(test_class.has_aux_tables?).to be true
end
it "returns specific configuration with aux_table_configuration" do
test_class.aux_table(:test_table) { |t| t.string :name }
config = test_class.aux_table_configuration(:test_table)
expect(config).to be_a(ActiveRecord::AuxTable::Configuration)
expect(config.table_name).to eq(:test_table)
end
it "returns all configurations with aux_table_configurations" do
test_class.aux_table(:test_table) { |t| t.string :name }
configs = test_class.aux_table_configurations
# Test retrieving all configurations
configs = TestClass.aux_table_configurations
expect(configs).to be_a(Hash)
expect(configs.keys).to include(:test_table)
end
describe "auxiliary model class generation" do
it "generates auxiliary model class dynamically" do
config = test_class.aux_table(:test_table) { |t| t.string :name }
it "generates auxiliary model class with proper configuration" do
config = TestClass.aux_table_configuration(:test_table)
# Test model class generation
expect(config.model_class).not_to be_nil
expect(config.model_class).to be_a(Class)
end
it "sets table name on generated model class" do
config = test_class.aux_table(:test_table) { |t| t.string :name }
# In test environment without ActiveRecord::Base, we manually set the table name
config.model_class.table_name = "test_table"
expect(config.model_class.table_name).to eq("test_table")
end
it "creates accessible constant for model class (when ActiveRecord is available)" do
# Skip this test in environments without ActiveRecord::Base
unless defined?(ActiveRecord::Base)
skip "ActiveRecord::Base not available in test environment"
end
test_class.aux_table(:car_aux) { |t| t.string :name }
it "creates accessible constant for model class" do
expect(Object.const_defined?("CarAux")).to be true
expect(Object.const_get("CarAux")).to be_a(Class)
expect(Object.const_get("CarAux")).to be < ActiveRecord::Base
end
it "prevents duplicate class name conflicts (when ActiveRecord is available)" do
# Skip this test in environments without ActiveRecord::Base
unless defined?(ActiveRecord::Base)
skip "ActiveRecord::Base not available in test environment"
end
it "prevents duplicate class name conflicts" do
# Define a constant to simulate conflict
Object.const_set("ConflictAux", Class.new)
expect {
test_class.aux_table(:conflict_aux) { |t| t.string :name }
TestClass.aux_table(:conflict_aux) { |t| t.string :name }
}.to raise_error(ArgumentError, "Class ConflictAux already exists")
end
it "generates simple class in test environment" do
# This test specifically verifies the fallback behavior
config = test_class.aux_table(:simple_table) { |t| t.string :name }
it "defines belongs_to association on auxiliary model class" do
config = Car.aux_table_configuration(:car_aux)
# Verify it's a simple class with table_name methods
expect(config.model_class).to respond_to(:table_name=)
expect(config.model_class).to respond_to(:table_name)
# Initially table_name should be nil
expect(config.model_class.table_name).to be_nil
# Should be able to set table name
config.model_class.table_name = "simple_table"
expect(config.model_class.table_name).to eq("simple_table")
# Verify the association is defined
association = config.model_class.reflect_on_association(:car)
expect(association).to be_present
expect(association.macro).to eq(:belongs_to)
expect(association.class_name).to eq("Car")
expect(association.foreign_key).to eq("vehicle_id")
end
after do
# Clean up constants created during tests
%w[TestTableAux CarAux ConflictAux].each do |const_name|
if Object.const_defined?(const_name)
Object.send(:remove_const, const_name)
end
end
it "sets up correct associations for STI classes" do
# Verify the association uses the base class table for the foreign key
association = Car.reflect_on_association(:car_aux)
expect(association).to be_present
expect(association.macro).to eq(:has_one)
expect(association.class_name).to eq("CarAux")
expect(association.foreign_key).to eq("vehicle_id")
end
end
describe "validation" do
it "raises TypeError for nil table name" do
expect {
test_class.aux_table(nil) { |t| t.string :name }
TestClass.aux_table(nil) { |t| t.string :name }
}.to raise_error(TypeError)
end
it "raises TypeError for invalid table name type" do
expect {
test_class.aux_table(123) { |t| t.string :name }
TestClass.aux_table(123) { |t| t.string :name }
}.to raise_error(TypeError)
end
it "raises TypeError when block is missing" do
expect { test_class.aux_table(:test_table) }.to raise_error(TypeError)
expect { TestClass.aux_table(:test_table) }.to raise_error(TypeError)
end
it "raises ArgumentError for duplicate table definitions" do
test_class.aux_table(:test_table) { |t| t.string :name }
expect {
test_class.aux_table(:test_table) { |t| t.string :description }
TestClass.aux_table(:test_table) { |t| t.string :description }
}.to raise_error(
ArgumentError,
"Auxiliary table 'test_table' is already defined"
@@ -261,18 +221,8 @@ RSpec.describe ActiveRecord::AuxTable do
end
describe "placeholder methods" do
let(:test_class) do
Class.new do
include ActiveRecord::AuxTable
def self.name
"TestClass"
end
end
end
it "aux_table_record method raises NotImplementedError" do
instance = test_class.new
instance = TestClass.new
expect { instance.aux_table_record(:test_table) }.to raise_error(
NotImplementedError,
"aux_table_record method not yet implemented"
@@ -280,11 +230,45 @@ RSpec.describe ActiveRecord::AuxTable do
end
it "build_aux_table_record method raises NotImplementedError" do
instance = test_class.new
instance = TestClass.new
expect { instance.build_aux_table_record(:test_table) }.to raise_error(
NotImplementedError,
"build_aux_table_record method not yet implemented"
)
end
end
describe "database integration" do
it "creates auxiliary model class with database operations and associations" do
config = Car.aux_table_configuration(:car_aux)
# Verify the auxiliary model class is properly configured
expect(config.model_class.column_names).to include(
"fuel_type",
"engine_size",
"vehicle_id"
)
# Test associations work correctly
vehicle = Vehicle.create!(name: "Toyota Camry", type: "Car")
# Create an auxiliary record associated with the vehicle
aux_record =
config.model_class.create!(
vehicle_id: vehicle.id,
fuel_type: "gasoline",
engine_size: 2.5
)
# Verify the association works
expect(aux_record.car).to eq(vehicle)
expect(aux_record.car.name).to eq("Toyota Camry")
expect(aux_record.fuel_type).to eq("gasoline")
expect(aux_record.engine_size).to eq(2.5)
# Verify we can find the auxiliary record through the association
found_aux = config.model_class.find_by(vehicle_id: vehicle.id)
expect(found_aux).to eq(aux_record)
end
end
end

View File

@@ -1,6 +1,15 @@
# frozen_string_literal: true
require "active_record"
require "active_record/aux_table"
require "active_record/errors"
require "pry"
# Configure ActiveRecord to use in-memory SQLite database
ActiveRecord::Base.establish_connection(
adapter: "sqlite3",
database: ":memory:"
)
RSpec.configure do |config|
# Enable flags like --only-failures and --next-failure
@@ -12,4 +21,11 @@ RSpec.configure do |config|
config.expect_with :rspec do |c|
c.syntax = :expect
end
config.around(:each) do |example|
ActiveRecord::Base.transaction do
example.run
raise ActiveRecord::Rollback
end
end
end