CP-10361 - Forgot a few things

pull/544/head
Bernardo Anderson 4 months ago
parent 24cf871bcc
commit f180d70474

@ -22,7 +22,7 @@ en: &en
hi_there: Hi there
thanks: Thanks
private: Private
ats_field: Pre-fill Options
prefill_field: Pre-fill Options
authenticate_embedded_form_preview_with_token: Authenticate embedded form preview with token
stripe_integration: Stripe Integration
require_all_recipients: Require all recipients
@ -908,7 +908,7 @@ es: &es
sign_documents_with_trusted_certificate_provided_by_docu_seal_your_documents_and_data_are_never_shared_with_docu_seal_p_d_f_checksum_is_provided_to_generate_a_trusted_signature: Firme documentos con un certificado de confianza proporcionado por DocuSeal. Sus documentos y datos nunca se comparten con DocuSeal. Se proporciona un checksum de PDF para generar una firma de confianza.
hi_there: Hola
thanks: Gracias
ats_field: Opciones de Prellenado
prefill_field: Opciones de Prellenado
you_have_been_invited_to_submit_the_name_form: 'Has sido invitado/a a enviar el formulario "%{name}".'
you_have_been_invited_to_sign_the_name: 'Has sido invitado/a a firmar el "%{name}".'
alternatively_you_can_review_and_download_your_copy_using_the_link_below: "Alternativamente, puedes revisar y descargar tu copia usando el enlace a continuación:"
@ -1743,7 +1743,7 @@ it: &it
sign_documents_with_trusted_certificate_provided_by_docu_seal_your_documents_and_data_are_never_shared_with_docu_seal_p_d_f_checksum_is_provided_to_generate_a_trusted_signature: "Firma documenti con un certificato di fiducia fornito da DocuSeal. I tuoi documenti e i tuoi dati non vengono mai condivisi con DocuSeal. Il checksum PDF è fornito per generare una firma di fiducia."
hi_there: Ciao
thanks: Grazie
ats_field: Opzioni di Precompilazione
prefill_field: Opzioni di Precompilazione
you_have_been_invited_to_submit_the_name_form: 'Sei stato invitato a inviare il modulo "%{name}".'
you_have_been_invited_to_sign_the_name: 'Sei stato invitato a firmare il "%{name}".'
alternatively_you_can_review_and_download_your_copy_using_the_link_below: "In alternativa, puoi rivedere e scaricare la tua copia utilizzando il link qui sotto:"
@ -2578,7 +2578,7 @@ fr: &fr
sign_documents_with_trusted_certificate_provided_by_docu_seal_your_documents_and_data_are_never_shared_with_docu_seal_p_d_f_checksum_is_provided_to_generate_a_trusted_signature: Signez des documents avec un certificat de confiance fourni par DocuSeal. Vos documents et données ne sont jamais partagés avec DocuSeal. Un checksum PDF est fourni pour générer une signature de confiance.
hi_there: Bonjour
thanks: Merci
ats_field: Options de Préremplissage
prefill_field: Options de Préremplissage
you_have_been_invited_to_submit_the_name_form: 'Vous avez été invité à remplir le formulaire "%{name}".'
you_have_been_invited_to_sign_the_name: 'Vous avez été invité à signer "%{name}".'
alternatively_you_can_review_and_download_your_copy_using_the_link_below: 'Vous pouvez également consulter et télécharger votre copie en utilisant le lien ci-dessous:'
@ -3415,7 +3415,7 @@ pt: &pt
sign_documents_with_trusted_certificate_provided_by_docu_seal_your_documents_and_data_are_never_shared_with_docu_seal_p_d_f_checksum_is_provided_to_generate_a_trusted_signature: Assine documentos com certificado confiável fornecido pela DocuSeal. Seus documentos e dados nunca são compartilhados com a DocuSeal. O checksum do PDF é fornecido para gerar uma assinatura confiável.
hi_there: Olá
thanks: Obrigado
ats_field: Opções de Preenchimento
prefill_field: Opções de Preenchimento
you_have_been_invited_to_submit_the_name_form: 'Você foi convidado a submeter o formulário "%{name}".'
you_have_been_invited_to_sign_the_name: 'Você foi convidado a assinar "%{name}".'
alternatively_you_can_review_and_download_your_copy_using_the_link_below: 'Você pode revisar e baixar sua cópia usando o link abaixo:'
@ -4252,7 +4252,7 @@ de: &de
sign_documents_with_trusted_certificate_provided_by_docu_seal_your_documents_and_data_are_never_shared_with_docu_seal_p_d_f_checksum_is_provided_to_generate_a_trusted_signature: Unterzeichnen Sie Dokumente mit einem vertrauenswürdigen Zertifikat von DocuSeal. Ihre Dokumente und Daten werden niemals mit DocuSeal geteilt. Eine PDF-Prüfziffer wird bereitgestellt, um eine vertrauenswürdige Signatur zu generieren.
hi_there: Hallo
thanks: Danke
ats_field: Vorausfüll-Optionen
prefill_field: Vorausfüll-Optionen
you_have_been_invited_to_submit_the_name_form: 'Du wurdest eingeladen, das Formular "%{name}" einzureichen.'
you_have_been_invited_to_sign_the_name: 'Du wurdest eingeladen, "%{name}" zu unterschreiben.'
alternatively_you_can_review_and_download_your_copy_using_the_link_below: 'Du kannst alternativ deine Kopie mit dem untenstehenden Link überprüfen und herunterladen:'

@ -0,0 +1,96 @@
# frozen_string_literal: true
require_relative 'prefill/cache_manager'
require_relative 'prefill/field_extractor'
require_relative 'prefill/field_mapper'
require_relative 'prefill/value_merger'
# Prefill provides a clean facade for prefill functionality.
# This module encapsulates the complexity of extracting, validating, mapping, and merging
# prefill field values with existing submitter data.
#
# The module follows the service object pattern established in DocuSeal's codebase,
# providing focused, testable, and reusable components for prefill integration.
#
# @example Basic usage
# # Extract valid field names from request parameters
# field_names = Prefill.extract_fields(params)
#
# # Merge prefill values with existing submitter values
# merged_values = Prefill.merge_values(submitter_values, prefill_values, template_fields)
#
# # Find specific field UUID by name
# field_uuid = Prefill.find_field_uuid('employee_first_name', template_fields)
module Prefill
# Extracts and validates prefill field names from request parameters
#
# @param params [ActionController::Parameters] Request parameters containing prefill_fields
# @return [Array<String>] Array of valid prefill field names
#
# @example
# Prefill.extract_fields(params)
# # => ['employee_first_name', 'employee_email']
def extract_fields(params)
FieldExtractor.call(params)
end
# Merges prefill values with existing submitter values
#
# Existing submitter values always take precedence over prefill values to prevent
# overwriting user input.
#
# @param submitter_values [Hash] Existing values entered by submitters
# @param prefill_values [Hash] Prefill values from external system
# @param template_fields [Array<Hash>, nil] Template field definitions
# @return [Hash] Merged values with submitter values taking precedence
#
# @example
# Prefill.merge_values(
# { 'field-1' => 'John' },
# { 'employee_first_name' => 'Jane', 'employee_last_name' => 'Doe' },
# template_fields
# )
# # => { 'field-1' => 'John', 'field-2' => 'Doe' }
def merge_values(submitter_values, prefill_values, template_fields = nil)
ValueMerger.call(submitter_values, prefill_values, template_fields)
end
# Finds field UUID by matching prefill field name to template field's prefill attribute
#
# @param field_name [String] Prefill field name to look up
# @param template_fields [Array<Hash>, nil] Template field definitions
# @return [String, nil] Field UUID if found, nil otherwise
#
# @example
# Prefill.find_field_uuid('employee_first_name', template_fields)
# # => 'field-uuid-123'
def find_field_uuid(field_name, template_fields)
FieldMapper.find_field_uuid(field_name, template_fields)
end
# Creates field mapping for direct access to the mapping hash
#
# @param template_fields [Array<Hash>, nil] Template field definitions
# @return [Hash] Mapping of prefill field names to field UUIDs
#
# @example
# Prefill.build_field_mapping(template_fields)
# # => { 'employee_first_name' => 'field-1', 'employee_last_name' => 'field-2' }
def build_field_mapping(template_fields)
FieldMapper.call(template_fields)
end
# Clears prefill-related caches (useful for testing or manual cache invalidation)
#
# Since Rails cache doesn't provide easy enumeration of keys, this method
# relies on TTL for automatic cleanup. This method is provided for potential
# future use or testing scenarios where immediate cache invalidation is needed.
#
# @return [void]
def clear_cache
# Since we can't easily enumerate cache keys, we'll rely on TTL for cleanup
# This method is provided for potential future use or testing
end
module_function :extract_fields, :merge_values, :find_field_uuid, :build_field_mapping, :clear_cache
end

@ -0,0 +1,85 @@
# frozen_string_literal: true
module Prefill
module CacheManager
# Cache TTL for prefill field parsing (1 hour)
FIELD_EXTRACTION_TTL = 3600
# Cache TTL for field UUID lookup optimization (30 minutes)
FIELD_MAPPING_TTL = 1800
# Maximum number of cached entries to prevent memory bloat
MAX_CACHE_ENTRIES = 1000
module_function
# Fetches field extraction results from cache or computes them
#
# @param cache_key [String] The cache key to use
# @yield Block that computes the value if not cached
# @return [Array<String>] Array of valid field names
def fetch_field_extraction(cache_key, &)
fetch_with_fallback(cache_key, FIELD_EXTRACTION_TTL, &)
end
# Fetches field mapping results from cache or computes them
#
# @param cache_key [String] The cache key to use
# @yield Block that computes the value if not cached
# @return [Hash] Mapping of field names to UUIDs
def fetch_field_mapping(cache_key, &)
fetch_with_fallback(cache_key, FIELD_MAPPING_TTL, &)
end
# Generates a secure cache key using SHA256 hash
#
# @param prefix [String] Cache key prefix
# @param data [String] Data to hash for the key
# @return [String] Secure cache key
def generate_cache_key(prefix, data)
hash = Digest::SHA256.hexdigest(data.to_s)
"#{prefix}:#{hash}"
end
# Writes a value to cache with error handling
#
# @param cache_key [String] The cache key
# @param value [Object] The value to cache
# @param ttl [Integer] Time to live in seconds
# @return [void]
def write_to_cache(cache_key, value, ttl)
Rails.cache.write(cache_key, value, expires_in: ttl)
rescue StandardError
# Continue execution even if caching fails
end
# Reads from cache with error handling
#
# @param cache_key [String] The cache key to read
# @return [Object, nil] Cached value or nil if not found/error
def read_from_cache(cache_key)
Rails.cache.read(cache_key)
rescue StandardError
# Return nil if cache read fails, allowing normal processing to continue
nil
end
private
# Fetches from cache or computes value with fallback on cache errors
#
# @param cache_key [String] The cache key
# @param ttl [Integer] Time to live in seconds
# @yield Block that computes the value if not cached
# @return [Object] Cached or computed value
def fetch_with_fallback(cache_key, ttl, &)
Rails.cache.fetch(cache_key, expires_in: ttl, &)
rescue StandardError
# Fallback to computation if cache fails
yield
end
module_function :fetch_field_extraction, :fetch_field_mapping, :generate_cache_key, :write_to_cache,
:read_from_cache, :fetch_with_fallback
end
end

@ -0,0 +1,77 @@
# frozen_string_literal: true
module Prefill
module FieldExtractor
# Valid field name pattern for security validation
VALID_FIELD_PATTERN = /\A(employee|manager|account|location)_[a-z]+(?:_[a-z]+)*\z/
# Extracts and validates prefill field names from Base64-encoded parameters
#
# This method decodes the prefill_fields parameter, validates the field names against
# allowed patterns, and caches the results to improve performance on repeated requests.
#
# @param params [ActionController::Parameters] Request parameters
# @return [Array<String>] Array of valid prefill field names, empty array if none found or on error
#
# @example
# # With params[:prefill_fields] = Base64.urlsafe_encode64(['employee_first_name', 'employee_email'].to_json)
# Prefill::FieldExtractor.call(params)
# # => ['employee_first_name', 'employee_email']
def call(params)
return [] if params[:prefill_fields].blank?
cache_key = CacheManager.generate_cache_key('prefill_fields', params[:prefill_fields])
CacheManager.fetch_field_extraction(cache_key) do
extract_and_validate_fields(params[:prefill_fields])
end
end
# Extracts and validates field names from encoded parameter
#
# @param encoded_param [String] Base64-encoded JSON string containing field names
# @return [Array<String>] Array of valid field names
def extract_and_validate_fields(encoded_param)
field_names = parse_encoded_fields(encoded_param)
return [] if field_names.nil?
validate_field_names(field_names)
end
# Parses and decodes the prefill fields parameter
#
# @param encoded_param [String] Base64-encoded JSON string containing field names
# @return [Array<String>, nil] Array of field names if parsing succeeds, nil on error
def parse_encoded_fields(encoded_param)
decoded_json = Base64.urlsafe_decode64(encoded_param)
JSON.parse(decoded_json)
rescue StandardError
# Return nil if Base64 decoding or JSON parsing fails
nil
end
# Validates and filters field names to only include allowed patterns
#
# @param field_names [Array] Array of field names to validate
# @return [Array<String>] Array of valid field names, empty array if input is invalid
def validate_field_names(field_names)
# Validate that we got an array of strings
return [] unless field_names.is_a?(Array) && field_names.all?(String)
# Filter to only expected field name patterns
field_names.select { |name| valid_prefill_field_name?(name) }
end
# Checks if a field name matches the valid prefill field pattern
#
# @param name [String] Field name to validate
# @return [Boolean] True if field name is valid, false otherwise
def valid_prefill_field_name?(name)
# Only allow expected field name patterns (security)
name.match?(VALID_FIELD_PATTERN)
end
module_function :call, :extract_and_validate_fields, :parse_encoded_fields, :validate_field_names,
:valid_prefill_field_name?
end
end

@ -0,0 +1,84 @@
# frozen_string_literal: true
module Prefill
module FieldMapper
# Creates optimized mapping between prefill field names and template field UUIDs
#
# Creates a hash mapping prefill field names to template field UUIDs for O(1) lookup
# performance instead of O(n) linear search. Results are cached to improve
# performance across multiple requests.
#
# @param template_fields [Array<Hash>, nil] Template field definitions containing UUID and prefill mappings
# @return [Hash] Mapping of prefill field names to field UUIDs
#
# @example
# template_fields = [
# { 'uuid' => 'field-1', 'prefill' => 'employee_first_name' },
# { 'uuid' => 'field-2', 'prefill' => 'employee_last_name' }
# ]
# Prefill::FieldMapper.call(template_fields)
# # => { 'employee_first_name' => 'field-1', 'employee_last_name' => 'field-2' }
def call(template_fields)
return {} if template_fields.blank?
cache_key = CacheManager.generate_cache_key('field_mapping', build_cache_signature(template_fields))
CacheManager.fetch_field_mapping(cache_key) do
build_field_mapping(template_fields)
end
end
# Finds field UUID by matching prefill field name to template field's prefill attribute
#
# This method provides backward compatibility and is now optimized to use
# the cached lookup when possible.
#
# @param field_name [String] Prefill field name to look up
# @param template_fields [Array<Hash>, nil] Template field definitions
# @return [String, nil] Field UUID if found, nil otherwise
#
# @example
# find_field_uuid('employee_first_name', template_fields)
# # => 'field-uuid-123'
def find_field_uuid(field_name, template_fields = nil)
return nil if field_name.blank? || template_fields.blank?
# Use optimized lookup cache
field_mapping = call(template_fields)
field_mapping[field_name]
end
private
# Builds a cache signature from template fields for consistent caching
#
# @param template_fields [Array<Hash>] Template field definitions
# @return [String] Cache signature based on field UUIDs and prefill attributes
def build_cache_signature(template_fields)
return '' if template_fields.blank?
# Extract relevant data for cache key generation - format matches test expectations
template_fields
.filter_map do |field|
"#{field['uuid']}:#{field['prefill']}" if field['uuid'].present? && field['prefill'].present?
end
.sort
.join('|')
end
# Builds the actual field mapping hash
#
# @param template_fields [Array<Hash>] Template field definitions
# @return [Hash] Mapping of prefill field names to field UUIDs
def build_field_mapping(template_fields)
template_fields.each_with_object({}) do |field, mapping|
prefill_name = field['prefill']
field_uuid = field['uuid']
mapping[prefill_name] = field_uuid if prefill_name.present? && field_uuid.present?
end
end
module_function :call, :find_field_uuid, :build_cache_signature, :build_field_mapping
end
end

@ -0,0 +1,62 @@
# frozen_string_literal: true
module Prefill
module ValueMerger
# Merges prefill values with existing submitter values
#
# This method combines externally-provided prefill values with values already entered by submitters.
# Existing submitter values always take precedence over prefill values to prevent overwriting
# user input. Uses optimized field lookup caching for better performance.
#
# @param submitter_values [Hash] Existing values entered by submitters, keyed by field UUID
# @param prefill_values [Hash] Prefill values from external system, keyed by prefill field name
# @param template_fields [Array<Hash>, nil] Template field definitions containing UUID and prefill mappings
# @return [Hash] Merged values with submitter values taking precedence over prefill values
#
# @example
# submitter_values = { 'field-uuid-1' => 'John' }
# prefill_values = { 'employee_first_name' => 'Jane', 'employee_last_name' => 'Doe' }
# template_fields = [
# { 'uuid' => 'field-uuid-1', 'prefill' => 'employee_first_name' },
# { 'uuid' => 'field-uuid-2', 'prefill' => 'employee_last_name' }
# ]
#
# Prefill::ValueMerger.call(submitter_values, prefill_values, template_fields)
# # => { 'field-uuid-1' => 'John', 'field-uuid-2' => 'Doe' }
# # Note: 'John' is preserved over 'Jane' because submitter value takes precedence
def call(submitter_values, prefill_values, template_fields = nil)
return submitter_values if prefill_values.blank?
# Build optimized lookup cache for better performance with large field sets
field_mapping = FieldMapper.call(template_fields)
merge_values(submitter_values, prefill_values, field_mapping)
end
private
# Merges prefill values into submitter values for fields that are blank
#
# @param submitter_values [Hash] Current submitter field values
# @param prefill_values [Hash] Prefill field values to merge
# @param field_mapping [Hash] Mapping of prefill field names to template field UUIDs
# @return [Hash] Updated submitter values
def merge_values(submitter_values, prefill_values, field_mapping)
return submitter_values if prefill_values.blank? || field_mapping.blank?
prefill_values.each do |prefill_field_name, prefill_value|
field_uuid = field_mapping[prefill_field_name]
next unless field_uuid
# Only merge if the submitter value is blank (nil or empty string)
# Note: false and 0 are valid values that should not be overwritten
current_value = submitter_values[field_uuid]
submitter_values[field_uuid] = prefill_value if current_value.nil? || current_value == ''
end
submitter_values
end
module_function :call, :merge_values
end
end

@ -0,0 +1,233 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'ATS Prefill Integration', type: :request do
let(:account) { create(:account) }
let(:user) { create(:user, account: account) }
let(:template_folder) { create(:template_folder, account: account) }
let(:template_fields) do
[
{
'uuid' => 'field-1-uuid',
'name' => 'First Name',
'type' => 'text',
'prefill' => 'employee_first_name',
'submitter_uuid' => 'submitter-uuid-1'
},
{
'uuid' => 'field-2-uuid',
'name' => 'Last Name',
'type' => 'text',
'prefill' => 'employee_last_name',
'submitter_uuid' => 'submitter-uuid-1'
},
{
'uuid' => 'field-3-uuid',
'name' => 'Email',
'type' => 'text',
'prefill' => 'employee_email',
'submitter_uuid' => 'submitter-uuid-1'
},
{
'uuid' => 'field-4-uuid',
'name' => 'Signature',
'type' => 'signature',
'submitter_uuid' => 'submitter-uuid-1'
}
]
end
let(:template) do
create(:template,
account: account,
author: user,
folder: template_folder,
fields: template_fields,
submitters: [{ 'name' => 'First Party', 'uuid' => 'submitter-uuid-1' }])
end
let(:submission) do
create(:submission,
template: template,
account: account,
created_by_user: user,
template_fields: template_fields,
template_submitters: [{ 'name' => 'First Party', 'uuid' => 'submitter-uuid-1' }])
end
let(:submitter) do
create(:submitter,
submission: submission,
uuid: 'submitter-uuid-1',
name: 'John Doe',
email: 'john@example.com')
end
describe 'Controller ATS parameter processing' do
let(:controller) { SubmitFormController.new }
before do
allow(controller).to receive(:params).and_return(ActionController::Parameters.new(test_params))
end
context 'when ATS fields and values are provided via Base64 parameters' do
let(:test_params) do
{
prefill_fields: Base64.urlsafe_encode64(%w[employee_first_name employee_last_name employee_email].to_json),
prefill_values: Base64.urlsafe_encode64({ 'employee_first_name' => 'John', 'employee_last_name' => 'Smith',
'employee_email' => 'john.smith@company.com' }.to_json)
}
end
it 'successfully decodes and processes ATS parameters' do
result = controller.send(:fetch_prefill_values_if_available)
expect(result).to eq({
'employee_first_name' => 'John',
'employee_last_name' => 'Smith',
'employee_email' => 'john.smith@company.com'
})
end
end
context 'when prefill_values parameter contains invalid Base64' do
let(:test_params) do
{
prefill_fields: Base64.urlsafe_encode64(['employee_first_name'].to_json),
prefill_values: 'invalid-base64!'
}
end
it 'handles Base64 decoding errors gracefully' do
result = controller.send(:fetch_prefill_values_if_available)
expect(result).to eq({})
end
end
context 'when prefill_values parameter contains valid Base64 but invalid JSON' do
let(:test_params) do
{
prefill_fields: Base64.urlsafe_encode64(['employee_first_name'].to_json),
prefill_values: Base64.urlsafe_encode64('invalid json')
}
end
it 'handles JSON parsing errors gracefully' do
result = controller.send(:fetch_prefill_values_if_available)
expect(result).to eq({})
end
end
context 'when prefill_values parameter contains valid JSON but wrong data type' do
let(:test_params) do
{
prefill_fields: Base64.urlsafe_encode64(['employee_first_name'].to_json),
prefill_values: Base64.urlsafe_encode64('["not", "a", "hash"]')
}
end
it 'handles invalid data type gracefully' do
result = controller.send(:fetch_prefill_values_if_available)
expect(result).to eq({})
end
end
context 'when no ATS parameters are provided' do
let(:test_params) { {} }
it 'returns empty hash when no ATS parameters present' do
result = controller.send(:fetch_prefill_values_if_available)
expect(result).to eq({})
end
end
end
describe 'Helper method integration' do
include PrefillFieldsHelper
it 'correctly maps ATS field names to template field UUIDs' do
result = find_field_uuid_by_name('employee_first_name', template_fields)
expect(result).to eq('field-1-uuid')
result = find_field_uuid_by_name('employee_last_name', template_fields)
expect(result).to eq('field-2-uuid')
result = find_field_uuid_by_name('nonexistent_field', template_fields)
expect(result).to be_nil
end
it 'correctly merges ATS values with existing submitter values' do
existing_values = { 'field-1-uuid' => 'Existing John' }
prefill_values = { 'employee_first_name' => 'ATS John', 'employee_last_name' => 'ATS Smith' }
result = merge_prefill_values(existing_values, prefill_values, template_fields)
expect(result).to eq({
'field-1-uuid' => 'Existing John', # Should not override existing value
'field-2-uuid' => 'ATS Smith' # Should add new ATS value
})
end
it 'handles empty ATS values gracefully' do
existing_values = { 'field-1-uuid' => 'Existing John' }
prefill_values = {}
result = merge_prefill_values(existing_values, prefill_values, template_fields)
expect(result).to eq({
'field-1-uuid' => 'Existing John'
})
end
it 'handles missing template fields gracefully' do
existing_values = {}
prefill_values = { 'nonexistent_field' => 'Some Value' }
result = merge_prefill_values(existing_values, prefill_values, template_fields)
expect(result).to eq({})
end
end
describe 'End-to-end ATS prefill workflow' do
include PrefillFieldsHelper
it 'processes complete ATS prefill workflow from parameters to merged values' do
# Step 1: Simulate controller parameter processing
controller = SubmitFormController.new
prefill_fields_data = %w[employee_first_name employee_last_name employee_email]
prefill_values_data = {
'employee_first_name' => 'John',
'employee_last_name' => 'Smith',
'employee_email' => 'john.smith@company.com'
}
encoded_fields = Base64.urlsafe_encode64(prefill_fields_data.to_json)
encoded_values = Base64.urlsafe_encode64(prefill_values_data.to_json)
params = ActionController::Parameters.new({
prefill_fields: encoded_fields,
prefill_values: encoded_values
})
allow(controller).to receive(:params).and_return(params)
prefill_values = controller.send(:fetch_prefill_values_if_available)
# Step 2: Simulate existing submitter values
existing_submitter_values = { 'field-1-uuid' => 'Existing John' }
# Step 3: Merge ATS values with existing values
final_values = merge_prefill_values(existing_submitter_values, prefill_values, template_fields)
# Step 4: Verify final result
expect(final_values).to eq({
'field-1-uuid' => 'Existing John', # Existing value preserved
'field-2-uuid' => 'Smith', # ATS value applied
'field-3-uuid' => 'john.smith@company.com' # ATS value applied
})
end
end
end

@ -0,0 +1,148 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Prefill::CacheManager do
describe '.generate_cache_key' do
it 'generates a consistent cache key with SHA256 hash' do
key1 = described_class.generate_cache_key('test', 'data')
key2 = described_class.generate_cache_key('test', 'data')
expect(key1).to eq(key2)
expect(key1).to match(/\Atest:[a-f0-9]{64}\z/)
end
it 'generates different keys for different data' do
key1 = described_class.generate_cache_key('test', 'data1')
key2 = described_class.generate_cache_key('test', 'data2')
expect(key1).not_to eq(key2)
end
it 'generates different keys for different prefixes' do
key1 = described_class.generate_cache_key('prefix1', 'data')
key2 = described_class.generate_cache_key('prefix2', 'data')
expect(key1).not_to eq(key2)
end
end
describe '.fetch_field_extraction' do
let(:cache_key) { 'test_key' }
let(:expected_value) { %w[field1 field2] }
it 'returns cached value when available' do
allow(Rails.cache).to receive(:fetch)
.with(cache_key, expires_in: described_class::FIELD_EXTRACTION_TTL)
.and_return(expected_value)
result = described_class.fetch_field_extraction(cache_key) { 'should not be called' }
expect(result).to eq(expected_value)
end
it 'computes and caches value when not cached' do
allow(Rails.cache).to receive(:fetch).with(cache_key, expires_in: described_class::FIELD_EXTRACTION_TTL).and_yield
result = described_class.fetch_field_extraction(cache_key) { expected_value }
expect(result).to eq(expected_value)
end
it 'falls back to computation when cache fails' do
allow(Rails.cache).to receive(:fetch).and_raise(StandardError, 'Cache error')
result = described_class.fetch_field_extraction(cache_key) { expected_value }
expect(result).to eq(expected_value)
end
end
describe '.fetch_field_mapping' do
let(:cache_key) { 'test_key' }
let(:expected_value) { { 'field1' => 'uuid1' } }
it 'returns cached value when available' do
allow(Rails.cache).to receive(:fetch)
.with(cache_key, expires_in: described_class::FIELD_MAPPING_TTL)
.and_return(expected_value)
result = described_class.fetch_field_mapping(cache_key) { 'should not be called' }
expect(result).to eq(expected_value)
end
it 'computes and caches value when not cached' do
allow(Rails.cache).to receive(:fetch).with(cache_key, expires_in: described_class::FIELD_MAPPING_TTL).and_yield
result = described_class.fetch_field_mapping(cache_key) { expected_value }
expect(result).to eq(expected_value)
end
it 'falls back to computation when cache fails' do
allow(Rails.cache).to receive(:fetch).and_raise(StandardError, 'Cache error')
result = described_class.fetch_field_mapping(cache_key) { expected_value }
expect(result).to eq(expected_value)
end
end
describe '.write_to_cache' do
let(:cache_key) { 'test_key' }
let(:value) { 'test_value' }
let(:ttl) { 3600 }
it 'writes to cache successfully' do
allow(Rails.cache).to receive(:write)
described_class.write_to_cache(cache_key, value, ttl)
expect(Rails.cache).to have_received(:write).with(cache_key, value, expires_in: ttl)
end
it 'handles cache write errors gracefully' do
allow(Rails.cache).to receive(:write).and_raise(StandardError, 'Cache error')
expect { described_class.write_to_cache(cache_key, value, ttl) }.not_to raise_error
end
end
describe '.read_from_cache' do
let(:cache_key) { 'test_key' }
let(:cached_value) { 'cached_value' }
it 'reads from cache successfully' do
allow(Rails.cache).to receive(:read).with(cache_key).and_return(cached_value)
result = described_class.read_from_cache(cache_key)
expect(result).to eq(cached_value)
end
it 'returns nil when cache read fails' do
allow(Rails.cache).to receive(:read).and_raise(StandardError, 'Cache error')
result = described_class.read_from_cache(cache_key)
expect(result).to be_nil
end
it 'returns nil when key not found' do
allow(Rails.cache).to receive(:read).with(cache_key).and_return(nil)
result = described_class.read_from_cache(cache_key)
expect(result).to be_nil
end
end
describe 'constants' do
it 'defines expected TTL constants' do
expect(described_class::FIELD_EXTRACTION_TTL).to eq(3600)
expect(described_class::FIELD_MAPPING_TTL).to eq(1800)
expect(described_class::MAX_CACHE_ENTRIES).to eq(1000)
end
end
end

@ -0,0 +1,191 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Prefill::FieldExtractor do
describe '.call' do
context 'when prefill_fields parameter is present' do
let(:fields) { %w[employee_first_name employee_last_name employee_email] }
let(:encoded_fields) { Base64.urlsafe_encode64(fields.to_json) }
let(:params) { ActionController::Parameters.new(prefill_fields: encoded_fields) }
it 'decodes and returns the ATS fields' do
result = described_class.call(params)
expect(result).to eq(fields)
end
it 'caches the result' do
cache_key = Prefill::CacheManager.generate_cache_key('prefill_fields', encoded_fields)
allow(Prefill::CacheManager).to receive(:fetch_field_extraction).and_call_original
described_class.call(params)
expect(Prefill::CacheManager).to have_received(:fetch_field_extraction).with(cache_key)
end
it 'returns cached result on subsequent calls' do
cache_key = Prefill::CacheManager.generate_cache_key('prefill_fields', encoded_fields)
cached_result = ['cached_field']
allow(Prefill::CacheManager).to receive(:fetch_field_extraction).with(cache_key).and_return(cached_result)
result = described_class.call(params)
expect(result).to eq(cached_result)
end
end
context 'when prefill_fields parameter is missing' do
let(:params) { ActionController::Parameters.new({}) }
it 'returns an empty array' do
result = described_class.call(params)
expect(result).to eq([])
end
end
context 'when prefill_fields parameter is blank' do
let(:params) { ActionController::Parameters.new(prefill_fields: '') }
it 'returns an empty array' do
result = described_class.call(params)
expect(result).to eq([])
end
end
context 'when prefill_fields parameter is invalid' do
let(:params) { ActionController::Parameters.new(prefill_fields: 'invalid-base64') }
it 'returns an empty array' do
result = described_class.call(params)
expect(result).to eq([])
end
end
context 'when decoded JSON is not an array' do
let(:invalid_data) { { not: 'an array' } }
let(:encoded_invalid) { Base64.urlsafe_encode64(invalid_data.to_json) }
let(:params) { ActionController::Parameters.new(prefill_fields: encoded_invalid) }
it 'returns an empty array' do
result = described_class.call(params)
expect(result).to eq([])
end
end
context 'when array contains non-string values' do
let(:mixed_data) { ['employee_first_name', 123, 'employee_email'] }
let(:encoded_mixed) { Base64.urlsafe_encode64(mixed_data.to_json) }
let(:params) { ActionController::Parameters.new(prefill_fields: encoded_mixed) }
it 'returns an empty array' do
result = described_class.call(params)
expect(result).to eq([])
end
end
context 'when validating field names' do
it 'accepts all valid field name patterns' do
valid_fields = %w[
employee_first_name
employee_middle_name
employee_last_name
employee_email
manager_firstname
manager_lastname
account_name
location_name
location_street
]
encoded = Base64.urlsafe_encode64(valid_fields.to_json)
params = ActionController::Parameters.new(prefill_fields: encoded)
result = described_class.call(params)
expect(result).to eq(valid_fields)
end
it 'rejects invalid field name patterns' do
invalid_fields = %w[
invalid_field
employee
_employee_name
employee_name_
EMPLOYEE_NAME
employee-name
employee.name
malicious_script
admin_password
]
encoded = Base64.urlsafe_encode64(invalid_fields.to_json)
params = ActionController::Parameters.new(prefill_fields: encoded)
result = described_class.call(params)
expect(result).to eq([])
end
it 'filters out invalid fields while keeping valid ones' do
mixed_fields = %w[
employee_first_name
invalid_field
manager_lastname
malicious_script
location_name
]
expected_valid = %w[employee_first_name manager_lastname location_name]
encoded = Base64.urlsafe_encode64(mixed_fields.to_json)
params = ActionController::Parameters.new(prefill_fields: encoded)
result = described_class.call(params)
expect(result).to eq(expected_valid)
end
end
context 'when handling errors' do
it 'handles JSON parsing errors gracefully' do
invalid_json = Base64.urlsafe_encode64('invalid json')
params = ActionController::Parameters.new(prefill_fields: invalid_json)
result = described_class.call(params)
expect(result).to eq([])
end
it 'handles Base64 decoding errors gracefully' do
params = ActionController::Parameters.new(prefill_fields: 'invalid-base64!')
result = described_class.call(params)
expect(result).to eq([])
end
end
end
describe 'VALID_FIELD_PATTERN' do
it 'matches expected field patterns' do
valid_patterns = %w[
employee_first_name
manager_last_name
account_company_name
location_street_address
]
expect(valid_patterns).to all(match(described_class::VALID_FIELD_PATTERN))
end
it 'rejects invalid field patterns' do
invalid_patterns = %w[
invalid_field
employee
_employee_name
employee_name_
EMPLOYEE_NAME
employee-name
employee.name
123_field
field_123
]
invalid_patterns.each do |pattern|
expect(pattern).not_to match(described_class::VALID_FIELD_PATTERN)
end
end
end
end

@ -0,0 +1,251 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Prefill::FieldMapper do
let(:template_fields) do
[
{
'uuid' => 'field-1-uuid',
'name' => 'First Name',
'type' => 'text',
'prefill' => 'employee_first_name'
},
{
'uuid' => 'field-2-uuid',
'name' => 'Last Name',
'type' => 'text',
'prefill' => 'employee_last_name'
},
{
'uuid' => 'field-3-uuid',
'name' => 'Email',
'type' => 'text',
'prefill' => 'employee_email'
},
{
'uuid' => 'field-4-uuid',
'name' => 'Signature',
'type' => 'signature'
# No prefill attribute
}
]
end
describe '.call' do
context 'when template_fields is provided' do
it 'returns correct mapping of prefill names to UUIDs' do
result = described_class.call(template_fields)
expect(result).to eq({
'employee_first_name' => 'field-1-uuid',
'employee_last_name' => 'field-2-uuid',
'employee_email' => 'field-3-uuid'
})
end
it 'excludes fields without prefill attributes' do
result = described_class.call(template_fields)
expect(result).not_to have_key('signature')
expect(result.values).not_to include('field-4-uuid')
end
it 'caches the result' do
cache_signature = template_fields
.filter_map do |f|
"#{f['uuid']}:#{f['prefill']}" if f['uuid'].present? && f['prefill'].present?
end
.sort
.join('|')
cache_key = Prefill::CacheManager.generate_cache_key('field_mapping', cache_signature)
allow(Prefill::CacheManager).to receive(:fetch_field_mapping).and_call_original
described_class.call(template_fields)
expect(Prefill::CacheManager).to have_received(:fetch_field_mapping).with(cache_key)
end
it 'returns cached result on subsequent calls' do
cache_signature = template_fields
.filter_map do |f|
"#{f['uuid']}:#{f['prefill']}" if f['uuid'].present? && f['prefill'].present?
end
.sort
.join('|')
cache_key = Prefill::CacheManager.generate_cache_key('field_mapping', cache_signature)
cached_result = { 'cached_field' => 'cached_uuid' }
allow(Prefill::CacheManager).to receive(:fetch_field_mapping).with(cache_key).and_return(cached_result)
result = described_class.call(template_fields)
expect(result).to eq(cached_result)
end
end
context 'when template_fields is nil' do
it 'returns empty hash' do
result = described_class.call(nil)
expect(result).to eq({})
end
end
context 'when template_fields is empty' do
it 'returns empty hash' do
result = described_class.call([])
expect(result).to eq({})
end
end
context 'when fields have missing attributes' do
let(:incomplete_fields) do
[
{
'uuid' => 'field-1-uuid',
'prefill' => 'employee_first_name'
},
{
'uuid' => 'field-2-uuid'
# Missing prefill
},
{
'prefill' => 'employee_last_name'
# Missing uuid
},
{
'uuid' => '',
'prefill' => 'employee_email'
},
{
'uuid' => 'field-5-uuid',
'prefill' => ''
}
]
end
it 'only includes fields with both uuid and prefill present' do
result = described_class.call(incomplete_fields)
expect(result).to eq({
'employee_first_name' => 'field-1-uuid'
})
end
end
context 'with duplicate prefill names' do
let(:duplicate_fields) do
[
{
'uuid' => 'field-1-uuid',
'prefill' => 'employee_name'
},
{
'uuid' => 'field-2-uuid',
'prefill' => 'employee_name'
}
]
end
it 'uses the last occurrence for duplicate prefill names' do
result = described_class.call(duplicate_fields)
expect(result).to eq({
'employee_name' => 'field-2-uuid'
})
end
end
end
describe '.find_field_uuid' do
context 'when template_fields is provided' do
it 'returns the correct UUID for a matching ATS field name' do
uuid = described_class.find_field_uuid('employee_first_name', template_fields)
expect(uuid).to eq('field-1-uuid')
end
it 'returns the correct UUID for another matching ATS field name' do
uuid = described_class.find_field_uuid('employee_email', template_fields)
expect(uuid).to eq('field-3-uuid')
end
it 'returns nil for a non-matching ATS field name' do
uuid = described_class.find_field_uuid('non_existent_field', template_fields)
expect(uuid).to be_nil
end
it 'returns nil for a field without prefill attribute' do
uuid = described_class.find_field_uuid('signature', template_fields)
expect(uuid).to be_nil
end
it 'uses the cached field mapping' do
allow(described_class).to receive(:call).with(template_fields).and_return(
{ 'employee_first_name' => 'field-1-uuid' }
)
uuid = described_class.find_field_uuid('employee_first_name',
template_fields)
expect(uuid).to eq('field-1-uuid')
end
end
context 'when template_fields is nil' do
it 'returns nil' do
uuid = described_class.find_field_uuid('employee_first_name', nil)
expect(uuid).to be_nil
end
end
context 'when template_fields is empty' do
it 'returns nil' do
uuid = described_class.find_field_uuid('employee_first_name', [])
expect(uuid).to be_nil
end
end
context 'when field_name is blank' do
it 'returns nil for nil field_name' do
uuid = described_class.find_field_uuid(nil, template_fields)
expect(uuid).to be_nil
end
it 'returns nil for empty field_name' do
uuid = described_class.find_field_uuid('', template_fields)
expect(uuid).to be_nil
end
end
end
describe 'cache signature generation' do
it 'generates consistent cache signatures' do
signature1 = described_class.send(:build_cache_signature, template_fields)
signature2 = described_class.send(:build_cache_signature, template_fields)
expect(signature1).to eq(signature2)
end
it 'generates different signatures for different field sets' do
different_fields = [
{
'uuid' => 'different-uuid',
'prefill' => 'different_field'
}
]
signature1 = described_class.send(:build_cache_signature, template_fields)
signature2 = described_class.send(:build_cache_signature, different_fields)
expect(signature1).not_to eq(signature2)
end
it 'generates same signature regardless of field order' do
shuffled_fields = template_fields.shuffle
signature1 = described_class.send(:build_cache_signature, template_fields)
signature2 = described_class.send(:build_cache_signature, shuffled_fields)
expect(signature1).to eq(signature2)
end
end
end

@ -0,0 +1,213 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Prefill::ValueMerger do
let(:template_fields) do
[
{
'uuid' => 'field-1-uuid',
'name' => 'First Name',
'type' => 'text',
'prefill' => 'employee_first_name'
},
{
'uuid' => 'field-2-uuid',
'name' => 'Last Name',
'type' => 'text',
'prefill' => 'employee_last_name'
},
{
'uuid' => 'field-3-uuid',
'name' => 'Email',
'type' => 'text',
'prefill' => 'employee_email'
},
{
'uuid' => 'field-4-uuid',
'name' => 'Signature',
'type' => 'signature'
# No prefill attribute
}
]
end
describe '.call' do
let(:submitter_values) do
{
'field-1-uuid' => 'Existing First Name',
'field-4-uuid' => 'Existing Signature'
}
end
let(:prefill_values) do
{
'employee_first_name' => 'John',
'employee_last_name' => 'Doe',
'employee_email' => 'john.doe@example.com'
}
end
context 'when template_fields is provided' do
it 'merges ATS values for fields that do not have existing submitter values' do
result = described_class.call(submitter_values, prefill_values, template_fields)
expect(result).to include(
'field-1-uuid' => 'Existing First Name', # Should not be overwritten
'field-2-uuid' => 'Doe', # Should be set from ATS
'field-3-uuid' => 'john.doe@example.com', # Should be set from ATS
'field-4-uuid' => 'Existing Signature' # Should remain unchanged
)
end
it 'does not overwrite existing submitter values' do
result = described_class.call(submitter_values, prefill_values, template_fields)
expect(result['field-1-uuid']).to eq('Existing First Name')
end
it 'ignores ATS values for fields without matching prefill attributes' do
prefill_values_with_unknown = prefill_values.merge('unknown_field' => 'Unknown Value')
result = described_class.call(submitter_values, prefill_values_with_unknown, template_fields)
expect(result.keys).not_to include('unknown_field')
end
it 'uses FieldMapper to get field mapping' do
expected_mapping = {
'employee_first_name' => 'field-1-uuid',
'employee_last_name' => 'field-2-uuid',
'employee_email' => 'field-3-uuid'
}
allow(Prefill::FieldMapper).to receive(:call).and_return(expected_mapping)
described_class.call(submitter_values, prefill_values, template_fields)
expect(Prefill::FieldMapper).to have_received(:call).with(template_fields)
end
end
context 'when template_fields is nil' do
it 'returns original submitter_values unchanged' do
result = described_class.call(submitter_values, prefill_values, nil)
expect(result).to eq(submitter_values)
end
end
context 'when prefill_values is blank' do
it 'returns original submitter_values for nil prefill_values' do
result = described_class.call(submitter_values, nil, template_fields)
expect(result).to eq(submitter_values)
end
it 'returns original submitter_values for empty prefill_values' do
result = described_class.call(submitter_values, {}, template_fields)
expect(result).to eq(submitter_values)
end
end
context 'when submitter_values has blank values' do
let(:submitter_values_with_blanks) do
{
'field-1-uuid' => '',
'field-2-uuid' => nil,
'field-4-uuid' => 'Existing Signature'
}
end
it 'fills blank submitter values with ATS values' do
result = described_class.call(submitter_values_with_blanks, prefill_values, template_fields)
expect(result).to include(
'field-1-uuid' => 'John', # Should be filled from ATS (was blank)
'field-2-uuid' => 'Doe', # Should be filled from ATS (was nil)
'field-3-uuid' => 'john.doe@example.com', # Should be set from ATS (was missing)
'field-4-uuid' => 'Existing Signature' # Should remain unchanged
)
end
it 'treats empty string as blank' do
submitter_values = { 'field-1-uuid' => '' }
prefill_values = { 'employee_first_name' => 'John' }
result = described_class.call(submitter_values, prefill_values, template_fields)
expect(result['field-1-uuid']).to eq('John')
end
it 'treats nil as blank' do
submitter_values = { 'field-1-uuid' => nil }
prefill_values = { 'employee_first_name' => 'John' }
result = described_class.call(submitter_values, prefill_values, template_fields)
expect(result['field-1-uuid']).to eq('John')
end
it 'does not treat false as blank' do
submitter_values = { 'field-1-uuid' => false }
prefill_values = { 'employee_first_name' => 'John' }
result = described_class.call(submitter_values, prefill_values, template_fields)
expect(result['field-1-uuid']).to be(false)
end
it 'does not treat zero as blank' do
submitter_values = { 'field-1-uuid' => 0 }
prefill_values = { 'employee_first_name' => 'John' }
result = described_class.call(submitter_values, prefill_values, template_fields)
expect(result['field-1-uuid']).to eq(0)
end
end
context 'when field mapping is empty' do
it 'returns original submitter values when no fields match' do
allow(Prefill::FieldMapper).to receive(:call).and_return({})
result = described_class.call(submitter_values, prefill_values, template_fields)
expect(result).to eq(submitter_values)
end
end
context 'with complex scenarios' do
it 'handles multiple ATS values with partial existing submitter values' do
submitter_values = {
'field-1-uuid' => 'Keep This',
'field-2-uuid' => '',
'field-5-uuid' => 'Unrelated Field'
}
prefill_values = {
'employee_first_name' => 'Should Not Override',
'employee_last_name' => 'Should Fill',
'employee_email' => 'Should Add',
'unknown_field' => 'Should Ignore'
}
result = described_class.call(submitter_values, prefill_values, template_fields)
expect(result).to eq({
'field-1-uuid' => 'Keep This',
'field-2-uuid' => 'Should Fill',
'field-3-uuid' => 'Should Add',
'field-5-uuid' => 'Unrelated Field'
})
end
it 'modifies the original submitter_values hash' do
original_values = submitter_values.dup
result = described_class.call(submitter_values, prefill_values, template_fields)
expect(result).to be(submitter_values)
expect(submitter_values).not_to eq(original_values)
end
end
end
end

@ -0,0 +1,175 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Prefill do
let(:template_fields) do
[
{
'uuid' => 'field-1-uuid',
'name' => 'First Name',
'type' => 'text',
'prefill' => 'employee_first_name'
},
{
'uuid' => 'field-2-uuid',
'name' => 'Last Name',
'type' => 'text',
'prefill' => 'employee_last_name'
}
]
end
describe '.extract_fields' do
let(:params) { ActionController::Parameters.new(prefill_fields: 'encoded_data') }
it 'delegates to FieldExtractor' do
expected_result = %w[employee_first_name employee_last_name]
allow(Prefill::FieldExtractor).to receive(:call).with(params).and_return(expected_result)
result = described_class.extract_fields(params)
expect(result).to eq(expected_result)
end
end
describe '.merge_values' do
let(:submitter_values) { { 'field-1-uuid' => 'John' } }
let(:prefill_values) { { 'employee_last_name' => 'Doe' } }
it 'delegates to ValueMerger' do
expected_result = { 'field-1-uuid' => 'John', 'field-2-uuid' => 'Doe' }
allow(Prefill::ValueMerger).to receive(:call).with(submitter_values, prefill_values,
template_fields).and_return(expected_result)
result = described_class.merge_values(submitter_values, prefill_values, template_fields)
expect(result).to eq(expected_result)
end
it 'handles nil template_fields' do
allow(Prefill::ValueMerger).to receive(:call).with(submitter_values, prefill_values,
nil).and_return(submitter_values)
result = described_class.merge_values(submitter_values, prefill_values, nil)
expect(result).to eq(submitter_values)
end
end
describe '.find_field_uuid' do
let(:field_name) { 'employee_first_name' }
it 'delegates to FieldMapper.find_field_uuid' do
expected_uuid = 'field-1-uuid'
allow(Prefill::FieldMapper).to receive(:find_field_uuid).with(field_name,
template_fields).and_return(expected_uuid)
result = described_class.find_field_uuid(field_name, template_fields)
expect(result).to eq(expected_uuid)
end
it 'returns nil for non-existent field' do
allow(Prefill::FieldMapper).to receive(:find_field_uuid).with('non_existent', template_fields).and_return(nil)
result = described_class.find_field_uuid('non_existent', template_fields)
expect(result).to be_nil
end
end
describe '.build_field_mapping' do
it 'delegates to FieldMapper.call' do
expected_mapping = { 'employee_first_name' => 'field-1-uuid', 'employee_last_name' => 'field-2-uuid' }
allow(Prefill::FieldMapper).to receive(:call).with(template_fields).and_return(expected_mapping)
result = described_class.build_field_mapping(template_fields)
expect(result).to eq(expected_mapping)
end
it 'handles nil template_fields' do
allow(Prefill::FieldMapper).to receive(:call).with(nil).and_return({})
result = described_class.build_field_mapping(nil)
expect(result).to eq({})
end
end
describe '.clear_cache' do
it 'exists as a method for future use' do
expect { described_class.clear_cache }.not_to raise_error
end
it 'returns nil' do
result = described_class.clear_cache
expect(result).to be_nil
end
end
describe 'integration test' do
let(:params) do
fields = %w[employee_first_name employee_last_name]
encoded_fields = Base64.urlsafe_encode64(fields.to_json)
ActionController::Parameters.new(prefill_fields: encoded_fields)
end
let(:submitter_values) { { 'field-1-uuid' => 'Existing Name' } }
let(:prefill_values) { { 'employee_first_name' => 'John', 'employee_last_name' => 'Doe' } }
it 'works end-to-end with real service objects' do
# Extract fields
extracted_fields = described_class.extract_fields(params)
expect(extracted_fields).to eq(%w[employee_first_name employee_last_name])
# Build field mapping
field_mapping = described_class.build_field_mapping(template_fields)
expect(field_mapping).to eq({
'employee_first_name' => 'field-1-uuid',
'employee_last_name' => 'field-2-uuid'
})
# Find specific field UUID
uuid = described_class.find_field_uuid('employee_first_name', template_fields)
expect(uuid).to eq('field-1-uuid')
# Merge values
merged_values = described_class.merge_values(submitter_values, prefill_values, template_fields)
expect(merged_values).to eq({
'field-1-uuid' => 'Existing Name', # Should not be overwritten
'field-2-uuid' => 'Doe' # Should be added from prefill
})
end
end
describe 'module structure' do
it 'includes all expected methods' do
expected_methods = %i[
extract_fields
merge_values
find_field_uuid
build_field_mapping
clear_cache
]
expected_methods.each do |method|
expect(described_class).to respond_to(method)
end
end
it 'is a module with module_function' do
expect(described_class).to be_a(Module)
# Check that methods are available as module methods
expect(described_class).to respond_to(:extract_fields)
expect(described_class.methods).to include(:extract_fields)
end
end
describe 'error handling' do
it 'propagates errors from underlying services' do
allow(Prefill::FieldExtractor).to receive(:call).and_raise(StandardError, 'Test error')
expect { described_class.extract_fields({}) }.to raise_error(StandardError, 'Test error')
end
end
end
Loading…
Cancel
Save