Files
fizzy/test/test_helper.rb
T
Mike Dalessio 0160f215f2 Validate email before creating identity during sign-up and sign-in
Avoid Sentry exceptions when attackers try to stuff invalid emails. The
browser performs form field validation that should normally prevent this
from occurring, so we just return 422 without validation error messages.

Also:

- extract redirect_to_session_magic_link helper
- some controller refactoring and cleanup
2025-12-06 16:52:16 -05:00

164 lines
5.3 KiB
Ruby

ENV["RAILS_ENV"] ||= "test"
require_relative "../config/environment"
require "rails/test_help"
require "webmock/minitest"
require "vcr"
require "mocha/minitest"
require "turbo/broadcastable/test_helper"
WebMock.allow_net_connect!
VCR.configure do |config|
config.allow_http_connections_when_no_cassette = true
config.cassette_library_dir = "test/vcr_cassettes"
config.hook_into :webmock
config.filter_sensitive_data("<OPEN_AI_KEY>") { Rails.application.credentials.openai_api_key || ENV["OPEN_AI_API_KEY"] }
config.default_cassette_options = {
match_requests_on: [ :method, :uri, :body ]
}
# Ignore timestamps in request bodies
config.before_record do |i|
if i.request&.body
i.request.body.gsub!(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} UTC/, "<TIME>")
end
end
config.register_request_matcher :body_without_times do |r1, r2|
b1 = (r1.body || "").gsub(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} UTC/, "<TIME>")
b2 = (r2.body || "").gsub(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} UTC/, "<TIME>")
b1 == b2
end
config.default_cassette_options = {
match_requests_on: [ :method, :uri, :body_without_times ]
}
end
module ActiveSupport
class TestCase
parallelize(workers: :number_of_processors)
# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
fixtures :all
include ActiveJob::TestHelper
include ActionTextTestHelper, CardTestHelper, ChangeTestHelper, SessionTestHelper
include Turbo::Broadcastable::TestHelper
setup do
Current.account = accounts("37s")
end
teardown do
Current.clear_all
end
end
end
class ActionDispatch::IntegrationTest
setup do
integration_session.default_url_options[:script_name] = "/#{ActiveRecord::FixtureSet.identify("37signals")}"
end
private
def without_action_dispatch_exception_handling
original = Rails.application.config.action_dispatch.show_exceptions
Rails.application.config.action_dispatch.show_exceptions = :none
yield
ensure
Rails.application.config.action_dispatch.show_exceptions = original
end
end
class ActionDispatch::SystemTestCase
setup do
self.default_url_options[:script_name] = "/#{ActiveRecord::FixtureSet.identify("37signals")}"
end
end
module FixturesTestHelper
extend ActiveSupport::Concern
class_methods do
def identify(label, column_type = :integer)
if label.to_s.end_with?("_uuid")
column_type = :uuid
label = label.to_s.delete_suffix("_uuid")
end
# Rails passes :string for varchar columns, so handle both :uuid and :string
return super(label, column_type) unless column_type.in?([ :uuid, :string ])
generate_fixture_uuid(label)
end
private
def generate_fixture_uuid(label)
# Generate deterministic UUIDv7 for fixtures that sorts by fixture ID
# This allows .first/.last to work as expected in tests
# Use the same CRC32 algorithm as Rails' default fixture ID generation
# so that UUIDs sort in the same order as integer IDs
fixture_int = Zlib.crc32("fixtures/#{label}") % (2**30 - 1)
# Translate the deterministic order into times in the past, so that records
# created during test runs are also always newer than the fixtures.
base_time = Time.utc(2024, 1, 1, 0, 0, 0)
timestamp = base_time + (fixture_int / 1000.0)
uuid_v7_with_timestamp(timestamp, label)
end
def uuid_v7_with_timestamp(time, seed_string)
# Generate UUIDv7 with custom timestamp and deterministic random bits
# Format: 48-bit timestamp_ms | 12-bit sub_ms_precision | 4-bit version | 62-bit random
time_ms = time.to_f * 1000
timestamp_ms = time_ms.to_i
# 48-bit timestamp (milliseconds since epoch)
bytes = []
bytes[0] = (timestamp_ms >> 40) & 0xff
bytes[1] = (timestamp_ms >> 32) & 0xff
bytes[2] = (timestamp_ms >> 24) & 0xff
bytes[3] = (timestamp_ms >> 16) & 0xff
bytes[4] = (timestamp_ms >> 8) & 0xff
bytes[5] = timestamp_ms & 0xff
# Use the 12-bit rand_a field for sub-millisecond precision
# Extract fractional milliseconds and convert to 12-bit value (0-4095)
# This gives us ~0.244 microsecond precision
frac_ms = time_ms - timestamp_ms
sub_ms_precision = (frac_ms * 4096).to_i & 0xfff
# Derive deterministic "random" bits from seed_string for the remaining random bits
hash = Digest::MD5.hexdigest(seed_string)
# 12-bit sub-ms precision + 4-bit version (0111 for v7)
bytes[6] = ((sub_ms_precision >> 8) & 0x0f) | 0x70 # version 7
bytes[7] = sub_ms_precision & 0xff
# 2-bit variant (10) + 62-bit random
rand_b = hash[3...19].to_i(16) & ((2**62) - 1)
bytes[8] = ((rand_b >> 56) & 0x3f) | 0x80 # variant 10
bytes[9] = (rand_b >> 48) & 0xff
bytes[10] = (rand_b >> 40) & 0xff
bytes[11] = (rand_b >> 32) & 0xff
bytes[12] = (rand_b >> 24) & 0xff
bytes[13] = (rand_b >> 16) & 0xff
bytes[14] = (rand_b >> 8) & 0xff
bytes[15] = rand_b & 0xff
# Format as UUID string and convert to base36 (25 chars)
uuid = "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x" % bytes
hex = uuid.delete("-")
hex.to_i(16).to_s(36).rjust(25, "0")
end
end
end
ActiveSupport.on_load(:active_record_fixture_set) do
prepend(FixturesTestHelper)
end