diff --git a/app/controllers/submit_form_controller.rb b/app/controllers/submit_form_controller.rb
index 9fdc9a0f..f7a0f19c 100644
--- a/app/controllers/submit_form_controller.rb
+++ b/app/controllers/submit_form_controller.rb
@@ -2,7 +2,7 @@
class SubmitFormController < ApplicationController
include PrefillFieldsHelper
-
+
layout 'form'
around_action :with_browser_locale, only: %i[show completed success]
@@ -105,13 +105,16 @@ class SubmitFormController < ApplicationController
end
def fetch_ats_prefill_values_if_available
- task_assignment_id = params[:task_assignment_id]
- return {} if task_assignment_id.blank?
+ # ATS passes values directly as Base64-encoded JSON parameters
+ return {} unless params[:ats_values].present?
begin
- fetch_ats_prefill_values(task_assignment_id)
+ decoded_json = Base64.urlsafe_decode64(params[:ats_values])
+ ats_values = JSON.parse(decoded_json)
+
+ # Validate that we got a hash
+ ats_values.is_a?(Hash) ? ats_values : {}
rescue StandardError => e
- Rails.logger.error "Error fetching ATS prefill values: #{e.message}"
{}
end
end
diff --git a/app/helpers/prefill_fields_helper.rb b/app/helpers/prefill_fields_helper.rb
index e616bbcd..d13caa79 100644
--- a/app/helpers/prefill_fields_helper.rb
+++ b/app/helpers/prefill_fields_helper.rb
@@ -16,18 +16,11 @@ module PrefillFieldsHelper
# Try to get from cache first with error handling
begin
cached_result = Rails.cache.read(cache_key)
- if cached_result
- Rails.logger.debug { "ATS fields cache hit for key: #{cache_key}" }
- return cached_result
- end
+ return cached_result if cached_result
rescue StandardError => e
- Rails.logger.warn "Cache read failed for ATS fields: #{e.message}"
# Continue with normal processing if cache read fails
end
- # Cache miss - perform expensive operations
- Rails.logger.debug { "ATS fields cache miss for key: #{cache_key}" }
-
begin
decoded_json = Base64.urlsafe_decode64(params[:ats_fields])
field_names = JSON.parse(decoded_json)
@@ -41,71 +34,34 @@ module PrefillFieldsHelper
# Cache the result with TTL (with error handling)
cache_result(cache_key, valid_fields, ATS_FIELDS_CACHE_TTL)
- # Log successful field reception
- Rails.logger.info "Processed and cached #{valid_fields.length} ATS prefill fields: #{valid_fields.join(', ')}"
-
valid_fields
rescue StandardError => e
- Rails.logger.warn "Failed to parse ATS prefill fields: #{e.message}"
# Cache empty result for failed parsing to avoid repeated failures
cache_result(cache_key, [], 5.minutes)
[]
end
end
- # Fetch actual prefill values from ATS for a specific task assignment
- # @param task_assignment_id [String, Integer] the ATS task assignment ID
- # @return [Hash] mapping of field names to actual values, empty hash if fetch fails
- def fetch_ats_prefill_values(task_assignment_id)
- return {} if task_assignment_id.blank?
-
- cache_key = "ats_prefill_values:#{task_assignment_id}"
-
- # Try cache first (short TTL for form session)
- begin
- cached_values = Rails.cache.read(cache_key)
- return cached_values if cached_values
- rescue StandardError => e
- Rails.logger.warn "Cache read failed for ATS prefill values: #{e.message}"
- end
-
- # Fetch from ATS API
- begin
- ats_api_url = Rails.application.config.ats_api_base_url || 'http://localhost:3000'
- response = fetch_from_ats_api("#{ats_api_url}/api/docuseal/#{task_assignment_id}/prefill")
-
- if response&.dig('values').is_a?(Hash)
- values = response['values']
- # Cache for form session duration (30 minutes)
- cache_result(cache_key, values, 30.minutes)
- Rails.logger.info "Fetched #{values.keys.length} prefill values for task_assignment #{task_assignment_id}"
- values
- else
- Rails.logger.warn "Invalid response format from ATS prefill API: #{response.inspect}"
- {}
- end
- rescue StandardError => e
- Rails.logger.error "Failed to fetch ATS prefill values for task_assignment #{task_assignment_id}: #{e.message}"
- {}
- end
- end
# Merge ATS prefill values with existing submitter values
# ATS values should not override existing submitter-entered values
# @param submitter_values [Hash] existing values entered by submitters
# @param ats_values [Hash] prefill values from ATS
# @return [Hash] merged values with submitter values taking precedence
- def merge_ats_prefill_values(submitter_values, ats_values)
+ def merge_ats_prefill_values(submitter_values, ats_values, template_fields = nil)
return submitter_values if ats_values.blank?
# Only use ATS values for fields that don't already have submitter values
ats_values.each do |field_name, value|
# Find matching field by name in template fields
- matching_field_uuid = find_field_uuid_by_name(field_name)
+ matching_field_uuid = find_field_uuid_by_name(field_name, template_fields)
+
next if matching_field_uuid.nil?
-
+
# Only set if submitter hasn't already filled this field
- submitter_values[matching_field_uuid] = value if submitter_values[matching_field_uuid].blank?
+ if submitter_values[matching_field_uuid].blank?
+ submitter_values[matching_field_uuid] = value
+ end
end
submitter_values
@@ -115,7 +71,6 @@ module PrefillFieldsHelper
def clear_ats_fields_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
- Rails.logger.info 'ATS fields cache clear requested (relies on TTL for cleanup)'
end
private
@@ -135,7 +90,6 @@ module PrefillFieldsHelper
def cache_result(cache_key, value, ttl)
Rails.cache.write(cache_key, value, expires_in: ttl)
rescue StandardError => e
- Rails.logger.warn "Cache write failed for ATS fields: #{e.message}"
# Continue execution even if caching fails
end
@@ -144,48 +98,14 @@ module PrefillFieldsHelper
[]
end
- # Find field UUID by matching field name/question_id
- # This is a simplified approach - in practice you might need more sophisticated matching
- def find_field_uuid_by_name(field_name)
- # This would need to access the current submission/template context
- # For now, we'll use a simple approach that would work with proper integration
- # In a real implementation, this would need template/submission context
-
- # Return nil for now - this needs proper integration with the submission context
- # The prefill values would need to be mapped by field UUID rather than field name
- Rails.logger.debug "Looking for field UUID for ATS field: #{field_name}"
- nil
- end
+ # Find field UUID by matching ATS field name to template field's prefill attribute
+ def find_field_uuid_by_name(field_name, template_fields = nil)
+ return nil if field_name.blank? || template_fields.blank?
- # Make HTTP request to ATS API
- def fetch_from_ats_api(url)
- require 'net/http'
- require 'json'
-
- uri = URI(url)
- http = Net::HTTP.new(uri.host, uri.port)
- http.use_ssl = uri.scheme == 'https'
- http.read_timeout = 10 # 10 second timeout
-
- request = Net::HTTP::Get.new(uri)
- request['Accept'] = 'application/json'
- request['Content-Type'] = 'application/json'
-
- # Add API authentication if configured
- if Rails.application.config.respond_to?(:ats_api_key) && Rails.application.config.ats_api_key.present?
- request['Authorization'] = "Bearer #{Rails.application.config.ats_api_key}"
- end
-
- response = http.request(request)
-
- if response.code == '200'
- JSON.parse(response.body)
- else
- Rails.logger.error "ATS API returned #{response.code}: #{response.body}"
- nil
- end
- rescue => e
- Rails.logger.error "HTTP request to ATS failed: #{e.message}"
- nil
+ # Find template field where the prefill attribute matches the ATS field name
+ matching_field = template_fields.find { |field| field['prefill'] == field_name }
+
+ matching_field&.dig('uuid')
end
+
end
diff --git a/app/views/submit_form/_submission_form.html.erb b/app/views/submit_form/_submission_form.html.erb
index bccf174b..b00e1989 100644
--- a/app/views/submit_form/_submission_form.html.erb
+++ b/app/views/submit_form/_submission_form.html.erb
@@ -2,4 +2,4 @@
<% data_fields = Submissions.filtered_conditions_fields(submitter).to_json %>
<% invite_submitters = (submitter.submission.template_submitters || submitter.submission.template.submitters).select { |s| s['invite_by_uuid'] == submitter.uuid && submitter.submission.submitters.none? { |e| e.uuid == s['uuid'] } }.to_json %>
<% optional_invite_submitters = (submitter.submission.template_submitters || submitter.submission.template.submitters).select { |s| s['optional_invite_by_uuid'] == submitter.uuid && submitter.submission.submitters.none? { |e| e.uuid == s['uuid'] } }.to_json %>
-
+
diff --git a/app/views/submit_form/show.html.erb b/app/views/submit_form/show.html.erb
index 7dd860ae..cf85c0e1 100644
--- a/app/views/submit_form/show.html.erb
+++ b/app/views/submit_form/show.html.erb
@@ -1,8 +1,10 @@
<% content_for(:html_title, "#{@submitter.submission.name || @submitter.submission.template.name} | DocuSeal") %>
<% content_for(:html_description, "#{@submitter.account.name} has invited you to fill and sign documents online effortlessly with a secure, fast, and user-friendly digital document signing solution.") %>
-<% fields_index = Templates.build_field_areas_index(@submitter.submission.template_fields || @submitter.submission.template.fields) %>
+<% template_fields = @submitter.submission.template_fields || @submitter.submission.template.fields %>
+<% fields_index = Templates.build_field_areas_index(template_fields) %>
<% submitter_values = @submitter.submission.submitters.reduce({}) { |acc, sub| acc.merge(sub.values) } %>
-<% values = merge_ats_prefill_values(submitter_values, @ats_prefill_values || {}) %>
+<% values = merge_ats_prefill_values(submitter_values, @ats_prefill_values || {}, template_fields) %>
+
<% submitters_index = @submitter.submission.submitters.index_by(&:uuid) %>
<% page_blob_struct = Struct.new(:url, :metadata, keyword_init: true) %>
<% schema = Submissions.filtered_conditions_schema(@submitter.submission, values:, include_submitter_uuid: @submitter.uuid) %>
@@ -103,7 +105,7 @@
- <%= render 'submit_form/submission_form', attachments_index: @attachments_index, submitter: @submitter, signature_attachment: @signature_attachment, configs: @form_configs, dry_run: local_assigns[:dry_run], expand: local_assigns[:expand], scroll_padding: local_assigns.fetch(:scroll_padding, '-110px'), schema: %>
+ <%= render 'submit_form/submission_form', attachments_index: @attachments_index, submitter: @submitter, signature_attachment: @signature_attachment, configs: @form_configs, dry_run: local_assigns[:dry_run], expand: local_assigns[:expand], scroll_padding: local_assigns.fetch(:scroll_padding, '-110px'), schema:, values: values %>
diff --git a/spec/helpers/prefill_fields_helper_spec.rb b/spec/helpers/prefill_fields_helper_spec.rb
index 14d79232..4ef177f8 100644
--- a/spec/helpers/prefill_fields_helper_spec.rb
+++ b/spec/helpers/prefill_fields_helper_spec.rb
@@ -3,348 +3,230 @@
require 'rails_helper'
RSpec.describe PrefillFieldsHelper, type: :helper do
- # Clear cache before each test to ensure clean state
- before do
- Rails.cache.clear
+ 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 '#extract_ats_prefill_fields' do
- it 'extracts valid field names from base64 encoded parameter' do
- fields = %w[employee_first_name employee_email manager_firstname]
- encoded = Base64.urlsafe_encode64(fields.to_json)
-
- allow(helper).to receive(:params).and_return({ ats_fields: encoded })
-
- result = helper.extract_ats_prefill_fields
- expect(result).to eq(fields)
- end
-
- it 'returns empty array for invalid base64' do
- allow(helper).to receive(:params).and_return({ ats_fields: 'invalid_base64' })
-
- result = helper.extract_ats_prefill_fields
- expect(result).to eq([])
- end
-
- it 'returns empty array for invalid JSON' do
- invalid_json = Base64.urlsafe_encode64('invalid json')
- allow(helper).to receive(:params).and_return({ ats_fields: invalid_json })
-
- result = helper.extract_ats_prefill_fields
- expect(result).to eq([])
- end
-
- it 'filters out invalid field names' do
- fields = %w[employee_first_name malicious_field account_name invalid-field]
- encoded = Base64.urlsafe_encode64(fields.to_json)
-
- allow(helper).to receive(:params).and_return({ ats_fields: encoded })
+ describe '#find_field_uuid_by_name' do
+ context 'when template_fields is provided' do
+ it 'returns the correct UUID for a matching ATS field name' do
+ uuid = helper.send(:find_field_uuid_by_name, 'employee_first_name', template_fields)
+ expect(uuid).to eq('field-1-uuid')
+ end
- result = helper.extract_ats_prefill_fields
- expect(result).to eq(%w[employee_first_name account_name])
- end
+ it 'returns the correct UUID for another matching ATS field name' do
+ uuid = helper.send(:find_field_uuid_by_name, 'employee_email', template_fields)
+ expect(uuid).to eq('field-3-uuid')
+ end
- it 'returns empty array when no ats_fields parameter' do
- allow(helper).to receive(:params).and_return({})
+ it 'returns nil for a non-matching ATS field name' do
+ uuid = helper.send(:find_field_uuid_by_name, 'non_existent_field', template_fields)
+ expect(uuid).to be_nil
+ end
- result = helper.extract_ats_prefill_fields
- expect(result).to eq([])
+ it 'returns nil for a field without prefill attribute' do
+ uuid = helper.send(:find_field_uuid_by_name, 'signature', template_fields)
+ expect(uuid).to be_nil
+ end
end
- it 'returns empty array when ats_fields parameter is empty' do
- allow(helper).to receive(:params).and_return({ ats_fields: '' })
-
- result = helper.extract_ats_prefill_fields
- expect(result).to eq([])
+ context 'when template_fields is nil' do
+ it 'returns nil' do
+ uuid = helper.send(:find_field_uuid_by_name, 'employee_first_name', nil)
+ expect(uuid).to be_nil
+ end
end
- it 'returns empty array when decoded JSON is not an array' do
- not_array = Base64.urlsafe_encode64({ field: 'employee_name' }.to_json)
- allow(helper).to receive(:params).and_return({ ats_fields: not_array })
-
- result = helper.extract_ats_prefill_fields
- expect(result).to eq([])
+ context 'when template_fields is empty' do
+ it 'returns nil' do
+ uuid = helper.send(:find_field_uuid_by_name, 'employee_first_name', [])
+ expect(uuid).to be_nil
+ end
end
- it 'returns empty array when array contains non-string values' do
- mixed_array = ['employee_first_name', 123, 'manager_firstname']
- encoded = Base64.urlsafe_encode64(mixed_array.to_json)
-
- allow(helper).to receive(:params).and_return({ ats_fields: encoded })
+ context 'when field_name is blank' do
+ it 'returns nil for nil field_name' do
+ uuid = helper.send(:find_field_uuid_by_name, nil, template_fields)
+ expect(uuid).to be_nil
+ end
- result = helper.extract_ats_prefill_fields
- expect(result).to eq([])
+ it 'returns nil for empty field_name' do
+ uuid = helper.send(:find_field_uuid_by_name, '', template_fields)
+ expect(uuid).to be_nil
+ end
end
+ end
- it 'accepts all valid field name patterns' do
- 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(fields.to_json)
-
- allow(helper).to receive(:params).and_return({ ats_fields: encoded })
-
- result = helper.extract_ats_prefill_fields
- expect(result).to eq(fields)
+ describe '#merge_ats_prefill_values' do
+ let(:submitter_values) do
+ {
+ 'field-1-uuid' => 'Existing First Name',
+ 'field-4-uuid' => 'Existing Signature'
+ }
end
- it 'logs successful field reception on cache miss' do
- fields = %w[employee_first_name employee_email]
- encoded = Base64.urlsafe_encode64(fields.to_json)
-
- allow(helper).to receive(:params).and_return({ ats_fields: encoded })
- allow(Rails.logger).to receive(:info)
- allow(Rails.logger).to receive(:debug)
-
- helper.extract_ats_prefill_fields
-
- expect(Rails.logger).to have_received(:info).with(
- 'Processed and cached 2 ATS prefill fields: employee_first_name, employee_email'
- )
+ let(:ats_values) do
+ {
+ 'employee_first_name' => 'John',
+ 'employee_last_name' => 'Doe',
+ 'employee_email' => 'john.doe@example.com'
+ }
end
- it 'logs parsing errors and caches empty result' do
- allow(helper).to receive(:params).and_return({ ats_fields: 'invalid_base64' })
- allow(Rails.logger).to receive(:warn)
- allow(Rails.logger).to receive(:debug)
-
- result = helper.extract_ats_prefill_fields
- expect(result).to eq([])
-
- expect(Rails.logger).to have_received(:warn).with(
- a_string_matching(/Failed to parse ATS prefill fields:/)
- )
- end
+ context 'when template_fields is provided' do
+ it 'merges ATS values for fields that do not have existing submitter values' do
+ result = helper.merge_ats_prefill_values(submitter_values, ats_values, template_fields)
- # Caching-specific tests
- describe 'caching behavior' do
- let(:fields) { %w[employee_first_name employee_email manager_firstname] }
- let(:encoded) { Base64.urlsafe_encode64(fields.to_json) }
-
- # Use memory store for caching tests since test environment uses null_store
- around do |example|
- original_cache = Rails.cache
- Rails.cache = ActiveSupport::Cache::MemoryStore.new
- example.run
- Rails.cache = original_cache
+ 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 'caches successful parsing results' do
- allow(helper).to receive(:params).and_return({ ats_fields: encoded })
- allow(Rails.logger).to receive(:info)
- allow(Rails.logger).to receive(:debug)
-
- # First call should parse and cache
- result1 = helper.extract_ats_prefill_fields
- expect(result1).to eq(fields)
+ it 'does not overwrite existing submitter values' do
+ result = helper.merge_ats_prefill_values(submitter_values, ats_values, template_fields)
- # Verify cache write occurred
- cache_key = helper.send(:ats_fields_cache_key, encoded)
- cached_value = Rails.cache.read(cache_key)
- expect(cached_value).to eq(fields)
+ expect(result['field-1-uuid']).to eq('Existing First Name')
end
- it 'returns cached results on subsequent calls' do
- allow(helper).to receive(:params).and_return({ ats_fields: encoded })
- allow(Rails.logger).to receive(:info)
- allow(Rails.logger).to receive(:debug)
+ it 'ignores ATS values for fields without matching prefill attributes' do
+ ats_values_with_unknown = ats_values.merge('unknown_field' => 'Unknown Value')
- # First call - cache miss
- result1 = helper.extract_ats_prefill_fields
- expect(result1).to eq(fields)
+ result = helper.merge_ats_prefill_values(submitter_values, ats_values_with_unknown, template_fields)
- # Verify cache miss was logged
- expect(Rails.logger).to have_received(:debug).at_least(:once) do |&block|
- block&.call&.include?('cache miss')
- end
-
- # Reset logger expectations
- allow(Rails.logger).to receive(:debug)
-
- # Second call - should be cache hit
- result2 = helper.extract_ats_prefill_fields
- expect(result2).to eq(fields)
-
- # Verify cache hit was logged
- expect(Rails.logger).to have_received(:debug).at_least(:once) do |&block|
- block&.call&.include?('cache hit')
- end
+ expect(result.keys).not_to include('unknown_field')
end
+ end
- it 'caches empty results for parsing errors' do
- allow(helper).to receive(:params).and_return({ ats_fields: 'invalid_base64' })
- allow(Rails.logger).to receive(:warn)
- allow(Rails.logger).to receive(:debug)
-
- # First call should fail and cache empty result
- result1 = helper.extract_ats_prefill_fields
- expect(result1).to eq([])
-
- # Verify empty result is cached
- cache_key = helper.send(:ats_fields_cache_key, 'invalid_base64')
- cached_value = Rails.cache.read(cache_key)
- expect(cached_value).to eq([])
-
- # Reset logger expectations
- allow(Rails.logger).to receive(:debug)
-
- # Second call should return cached empty result
- result2 = helper.extract_ats_prefill_fields
- expect(result2).to eq([])
-
- # Verify cache hit was logged
- expect(Rails.logger).to have_received(:debug).at_least(:once) do |&block|
- block&.call&.include?('cache hit')
- end
+ context 'when template_fields is nil' do
+ it 'returns original submitter_values unchanged' do
+ result = helper.merge_ats_prefill_values(submitter_values, ats_values, nil)
+ expect(result).to eq(submitter_values)
end
+ end
- it 'generates consistent cache keys for same input' do
- key1 = helper.send(:ats_fields_cache_key, encoded)
- key2 = helper.send(:ats_fields_cache_key, encoded)
-
- expect(key1).to eq(key2)
- expect(key1).to start_with('ats_fields:')
- expect(key1.length).to be > 20 # Should be a reasonable hash length
+ context 'when ats_values is blank' do
+ it 'returns original submitter_values for nil ats_values' do
+ result = helper.merge_ats_prefill_values(submitter_values, nil, template_fields)
+ expect(result).to eq(submitter_values)
end
- it 'generates different cache keys for different inputs' do
- fields2 = %w[manager_lastname location_name]
- encoded2 = Base64.urlsafe_encode64(fields2.to_json)
-
- key1 = helper.send(:ats_fields_cache_key, encoded)
- key2 = helper.send(:ats_fields_cache_key, encoded2)
-
- expect(key1).not_to eq(key2)
+ it 'returns original submitter_values for empty ats_values' do
+ result = helper.merge_ats_prefill_values(submitter_values, {}, template_fields)
+ expect(result).to eq(submitter_values)
end
+ end
- it 'respects cache TTL for successful results' do
- allow(helper).to receive(:params).and_return({ ats_fields: encoded })
- allow(Rails.cache).to receive(:write).and_call_original
-
- helper.extract_ats_prefill_fields
-
- expect(Rails.cache).to have_received(:write).with(
- anything,
- fields,
- expires_in: PrefillFieldsHelper::ATS_FIELDS_CACHE_TTL
- )
+ 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 'uses shorter TTL for error results' do
- allow(helper).to receive(:params).and_return({ ats_fields: 'invalid_base64' })
- allow(Rails.cache).to receive(:write).and_call_original
- allow(Rails.logger).to receive(:warn)
-
- helper.extract_ats_prefill_fields
+ it 'fills blank submitter values with ATS values' do
+ result = helper.merge_ats_prefill_values(submitter_values_with_blanks, ats_values, template_fields)
- expect(Rails.cache).to have_received(:write).with(
- anything,
- [],
- expires_in: 5.minutes
+ 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
+ end
+ end
- it 'handles cache read failures gracefully' do
- allow(helper).to receive(:params).and_return({ ats_fields: encoded })
- allow(Rails.cache).to receive(:read).and_raise(StandardError.new('Cache error'))
- allow(Rails.logger).to receive(:info)
- allow(Rails.logger).to receive(:debug)
- allow(Rails.logger).to receive(:warn)
-
- # Should fall back to normal processing
- result = helper.extract_ats_prefill_fields
- expect(result).to eq(fields)
- expect(Rails.logger).to have_received(:warn).with('Cache read failed for ATS fields: Cache error')
- end
+ describe '#extract_ats_prefill_fields' do
+ before do
+ allow(helper).to receive(:params).and_return(params)
+ end
- it 'handles cache write failures gracefully' do
- allow(helper).to receive(:params).and_return({ ats_fields: encoded })
- allow(Rails.cache).to receive(:write).and_raise(StandardError.new('Cache error'))
- allow(Rails.logger).to receive(:info)
- allow(Rails.logger).to receive(:debug)
- allow(Rails.logger).to receive(:warn)
+ context 'when ats_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) { { ats_fields: encoded_fields } }
- # Should still return correct result even if caching fails
+ it 'decodes and returns the ATS fields' do
result = helper.extract_ats_prefill_fields
expect(result).to eq(fields)
- expect(Rails.logger).to have_received(:warn).with('Cache write failed for ATS fields: Cache error')
- end
- end
-
- describe 'performance characteristics' do
- let(:fields) { %w[employee_first_name employee_email manager_firstname] }
- let(:encoded) { Base64.urlsafe_encode64(fields.to_json) }
-
- # Use memory store for performance tests since test environment uses null_store
- around do |example|
- original_cache = Rails.cache
- Rails.cache = ActiveSupport::Cache::MemoryStore.new
- example.run
- Rails.cache = original_cache
end
- it 'avoids expensive operations on cache hits' do
- allow(helper).to receive(:params).and_return({ ats_fields: encoded })
- allow(Rails.logger).to receive(:info)
- allow(Rails.logger).to receive(:debug)
-
- # First call to populate cache
+ it 'caches the result' do
+ # The implementation uses a SHA256 hash for cache key, not the raw encoded string
+ cache_key = helper.send(:ats_fields_cache_key, encoded_fields)
+ expect(Rails.cache).to receive(:read).with(cache_key).and_return(nil)
+ expect(Rails.cache).to receive(:write).with(cache_key, fields, expires_in: 1.hour)
helper.extract_ats_prefill_fields
+ end
+ end
- # Mock expensive operations to verify they're not called on cache hit
- allow(Base64).to receive(:urlsafe_decode64).and_call_original
- allow(JSON).to receive(:parse).and_call_original
+ context 'when ats_fields parameter is missing' do
+ let(:params) { {} }
- # Second call should use cache
+ it 'returns an empty array' do
result = helper.extract_ats_prefill_fields
- expect(result).to eq(fields)
-
- # Verify expensive operations were not called on second call
- expect(Base64).not_to have_received(:urlsafe_decode64)
- expect(JSON).not_to have_received(:parse)
+ expect(result).to eq([])
end
end
- end
- describe '#valid_ats_field_name?' do
- it 'returns true for valid employee field names' do
- expect(helper.send(:valid_ats_field_name?, 'employee_first_name')).to be true
- expect(helper.send(:valid_ats_field_name?, 'employee_email')).to be true
- expect(helper.send(:valid_ats_field_name?, 'employee_phone_number')).to be true
- end
+ context 'when ats_fields parameter is invalid' do
+ let(:params) { { ats_fields: 'invalid-base64' } }
- it 'returns true for valid manager field names' do
- expect(helper.send(:valid_ats_field_name?, 'manager_firstname')).to be true
- expect(helper.send(:valid_ats_field_name?, 'manager_lastname')).to be true
- expect(helper.send(:valid_ats_field_name?, 'manager_email')).to be true
+ it 'returns an empty array' do
+ result = helper.extract_ats_prefill_fields
+ expect(result).to eq([])
+ end
end
- it 'returns true for valid account field names' do
- expect(helper.send(:valid_ats_field_name?, 'account_name')).to be true
- expect(helper.send(:valid_ats_field_name?, 'account_id')).to be true
- end
+ it 'accepts all valid field name patterns' do
+ 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(fields.to_json)
- it 'returns true for valid location field names' do
- expect(helper.send(:valid_ats_field_name?, 'location_name')).to be true
- expect(helper.send(:valid_ats_field_name?, 'location_street')).to be true
- expect(helper.send(:valid_ats_field_name?, 'location_city')).to be true
- end
+ allow(helper).to receive(:params).and_return({ ats_fields: encoded })
- it 'returns false for invalid field names' do
- expect(helper.send(:valid_ats_field_name?, 'malicious_field')).to be false
- expect(helper.send(:valid_ats_field_name?, 'invalid-field')).to be false
- expect(helper.send(:valid_ats_field_name?, 'EMPLOYEE_NAME')).to be false
- expect(helper.send(:valid_ats_field_name?, 'employee')).to be false
- expect(helper.send(:valid_ats_field_name?, 'employee_')).to be false
- expect(helper.send(:valid_ats_field_name?, '_employee_name')).to be false
+ result = helper.extract_ats_prefill_fields
+ expect(result).to eq(fields)
end
end
+
end
diff --git a/spec/integration/ats_prefill_integration_spec.rb b/spec/integration/ats_prefill_integration_spec.rb
new file mode 100644
index 00000000..ad208116
--- /dev/null
+++ b/spec/integration/ats_prefill_integration_spec.rb
@@ -0,0 +1,221 @@
+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
+ {
+ ats_fields: Base64.urlsafe_encode64(['employee_first_name', 'employee_last_name', 'employee_email'].to_json),
+ ats_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_ats_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 ats_values parameter contains invalid Base64' do
+ let(:test_params) do
+ {
+ ats_fields: Base64.urlsafe_encode64(['employee_first_name'].to_json),
+ ats_values: 'invalid-base64!'
+ }
+ end
+
+ it 'handles Base64 decoding errors gracefully' do
+ result = controller.send(:fetch_ats_prefill_values_if_available)
+ expect(result).to eq({})
+ end
+ end
+
+ context 'when ats_values parameter contains valid Base64 but invalid JSON' do
+ let(:test_params) do
+ {
+ ats_fields: Base64.urlsafe_encode64(['employee_first_name'].to_json),
+ ats_values: Base64.urlsafe_encode64('invalid json')
+ }
+ end
+
+ it 'handles JSON parsing errors gracefully' do
+ result = controller.send(:fetch_ats_prefill_values_if_available)
+ expect(result).to eq({})
+ end
+ end
+
+ context 'when ats_values parameter contains valid JSON but wrong data type' do
+ let(:test_params) do
+ {
+ ats_fields: Base64.urlsafe_encode64(['employee_first_name'].to_json),
+ ats_values: Base64.urlsafe_encode64('["not", "a", "hash"]')
+ }
+ end
+
+ it 'handles invalid data type gracefully' do
+ result = controller.send(:fetch_ats_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_ats_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' }
+ ats_values = { 'employee_first_name' => 'ATS John', 'employee_last_name' => 'ATS Smith' }
+
+ result = merge_ats_prefill_values(existing_values, ats_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' }
+ ats_values = {}
+
+ result = merge_ats_prefill_values(existing_values, ats_values, template_fields)
+
+ expect(result).to eq({
+ 'field-1-uuid' => 'Existing John'
+ })
+ end
+
+ it 'handles missing template fields gracefully' do
+ existing_values = {}
+ ats_values = { 'nonexistent_field' => 'Some Value' }
+
+ result = merge_ats_prefill_values(existing_values, ats_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
+ allow(controller).to receive(:params).and_return(ActionController::Parameters.new({
+ ats_fields: Base64.urlsafe_encode64(['employee_first_name', 'employee_last_name', 'employee_email'].to_json),
+ ats_values: Base64.urlsafe_encode64({ 'employee_first_name' => 'John', 'employee_last_name' => 'Smith', 'employee_email' => 'john.smith@company.com' }.to_json)
+ }))
+
+ ats_values = controller.send(:fetch_ats_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_ats_prefill_values(existing_submitter_values, ats_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