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 @@ 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