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
2 changes: 1 addition & 1 deletion lib/fixture_kit/coders/active_record_coder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def generate_statements(models)
rows = []
model.unscoped.order(:id).find_each do |record|
row_values = column_names.map do |col|
value = record.read_attribute_before_type_cast(col)
value = record.read_attribute_for_database(col)
model.connection.quote(value)
end
rows << "(#{row_values.join(", ")})"
Expand Down
6 changes: 6 additions & 0 deletions spec/dummy/app/models/binary_blob.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# frozen_string_literal: true

class BinaryBlob < ActiveRecord::Base
has_many :children, class_name: "BinaryBlobChild"
encrypts :secret_note
end
5 changes: 5 additions & 0 deletions spec/dummy/app/models/binary_blob_child.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

class BinaryBlobChild < ActiveRecord::Base
belongs_to :binary_blob
end
4 changes: 4 additions & 0 deletions spec/dummy/config/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,9 @@ class Application < Rails::Application

# Minimal config for testing
config.active_record.maintain_test_schema = false

config.active_record.encryption.primary_key = "test_primary_key_32_bytes_padding"
config.active_record.encryption.deterministic_key = "test_deterministic_key_32_padding"
config.active_record.encryption.key_derivation_salt = "test_key_derivation_salt_padding"
end
end
16 changes: 16 additions & 0 deletions spec/support/dummy_rails_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ def setup_databases
ActiveRecord::Base.connection.drop_table(:vehicles, if_exists: true, force: :cascade)
ActiveRecord::Base.connection.drop_table(:gadgets, if_exists: true, force: :cascade)
ActiveRecord::Base.connection.drop_table(:computed_widgets, if_exists: true, force: :cascade)
ActiveRecord::Base.connection.drop_table(:binary_blob_children, if_exists: true, force: :cascade)
ActiveRecord::Base.connection.drop_table(:binary_blobs, if_exists: true, force: :cascade)
end

AnalyticsRecord.connection.disable_referential_integrity do
Expand Down Expand Up @@ -143,6 +145,20 @@ def setup_databases
t.timestamps
end

ActiveRecord::Base.connection.create_table :binary_blobs, force: true do |t|
t.binary :payload, limit: 16, null: false
t.string :label, null: false
t.json :metadata
t.text :secret_note
t.timestamps
end

ActiveRecord::Base.connection.create_table :binary_blob_children, force: true do |t|
t.binary :payload, limit: 16, null: false
t.references :binary_blob, null: false, foreign_key: true
t.timestamps
end

# Analytics database schema
AnalyticsRecord.connection.create_table :activity_logs, force: true do |t|
t.integer :external_user_id, null: false
Expand Down
65 changes: 65 additions & 0 deletions spec/unit/coders/active_record_coder_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,71 @@ def upsert_all_options(model)
end
end

describe "binary column encoding" do
let(:raw_bytes) { (240..255).map(&:chr).join.force_encoding(Encoding::ASCII_8BIT) }

it "preserves exact bytes when fixtures are mounted from cache" do
result = coder.generate { BinaryBlob.create!(payload: raw_bytes, label: "first") }

coder.mount(result)

replayed = BinaryBlob.find_by!(label: "first")
expect(replayed.payload.b).to eq(raw_bytes)
end

it "round-trips multiple rows with different payloads" do
other_bytes = (128..143).map(&:chr).join.force_encoding(Encoding::ASCII_8BIT)
result = coder.generate do
BinaryBlob.create!(payload: raw_bytes, label: "first")
BinaryBlob.create!(payload: other_bytes, label: "second")
end

coder.mount(result)

expect(BinaryBlob.find_by!(label: "first").payload.b).to eq(raw_bytes)
expect(BinaryBlob.find_by!(label: "second").payload.b).to eq(other_bytes)
end

it "round-trips a JSON column alongside binary data" do
metadata = {"version" => 2, "tags" => ["alpha", "beta"]}
result = coder.generate do
BinaryBlob.create!(payload: raw_bytes, label: "with-json", metadata: metadata)
end

coder.mount(result)

replayed = BinaryBlob.find_by!(label: "with-json")
expect(replayed.payload.b).to eq(raw_bytes)
expect(replayed.metadata).to eq(metadata)
end

it "stores ciphertext, not cleartext, for encrypts attributes" do
cleartext = "shhh-this-is-private-#{SecureRandom.hex(4)}"
result = coder.generate { BinaryBlob.create!(payload: raw_bytes, label: "encrypted", secret_note: cleartext) }

sql = result[BinaryBlob]
expect(sql).not_to include(cleartext)

coder.mount(result)

replayed = BinaryBlob.find_by!(label: "encrypted")
expect(replayed.secret_note).to eq(cleartext)
end

it "replays a foreign key relationship without integrity errors" do
result = coder.generate do
parent = BinaryBlob.create!(payload: raw_bytes, label: "parent")
BinaryBlobChild.create!(binary_blob: parent, payload: raw_bytes)
end

coder.mount(result)

parent = BinaryBlob.find_by!(label: "parent")
expect(parent.children.count).to eq(1)
expect(parent.children.first.payload.b).to eq(raw_bytes)
end
end
Comment on lines +118 to +181
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Minor | [fresh_eyes]: test-coverage

PR description calls out encrypted attributes as a security concern (cleartext was emitted before this fix), but no test verifies that encrypts :col now round-trips ciphertext correctly through generate/mount. A dedicated test would guard against regression on the security-sensitive path.


🙏🏽 Please 👍🏽 👎🏽 if you found this useful. Generated by Fresh Eyes Reviewer. Get help in #ai-code-reviews
💡 Tip: You can create custom checks for your project — see repository checks.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in e012bcf

Comment on lines +118 to +181
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Minor | [fresh_eyes]: test-coverage

PR description claims a test exists for custom Attribute API types ('UuidBlob#external_id modeled on mysql-binuuid-rails serializes UUID-string -> bytes correctly through the cache') but no UuidBlob model or test case is present in the codebase. This is a claimed-but-missing test for a serialization path the PR explicitly calls out as previously broken.


🙏🏽 Please 👍🏽 👎🏽 if you found this useful. Generated by Fresh Eyes Reviewer. Get help in #ai-code-reviews
💡 Tip: You can create custom checks for your project — see repository checks.


describe "#mount" do
it "documents that connection execute_batch is currently private" do
connection = User.connection
Expand Down
Loading