mirror of https://github.com/docusealco/docuseal
CP-10361 - Due to Docuseal being a public repo, we want to rename these values/fields to be generic instead of labeling 'ats'
parent
d4a0dd379a
commit
24cf871bcc
@ -1,96 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require_relative 'ats_prefill/cache_manager'
|
|
||||||
require_relative 'ats_prefill/field_extractor'
|
|
||||||
require_relative 'ats_prefill/field_mapper'
|
|
||||||
require_relative 'ats_prefill/value_merger'
|
|
||||||
|
|
||||||
# AtsPrefill provides a clean facade for ATS (Applicant Tracking System) prefill functionality.
|
|
||||||
# This module encapsulates the complexity of extracting, validating, mapping, and merging
|
|
||||||
# ATS 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 ATS integration.
|
|
||||||
#
|
|
||||||
# @example Basic usage
|
|
||||||
# # Extract valid field names from request parameters
|
|
||||||
# field_names = AtsPrefill.extract_fields(params)
|
|
||||||
#
|
|
||||||
# # Merge ATS values with existing submitter values
|
|
||||||
# merged_values = AtsPrefill.merge_values(submitter_values, ats_values, template_fields)
|
|
||||||
#
|
|
||||||
# # Find specific field UUID by name
|
|
||||||
# field_uuid = AtsPrefill.find_field_uuid('employee_first_name', template_fields)
|
|
||||||
module AtsPrefill
|
|
||||||
# Extracts and validates ATS field names from request parameters
|
|
||||||
#
|
|
||||||
# @param params [ActionController::Parameters] Request parameters containing ats_fields
|
|
||||||
# @return [Array<String>] Array of valid ATS field names
|
|
||||||
#
|
|
||||||
# @example
|
|
||||||
# AtsPrefill.extract_fields(params)
|
|
||||||
# # => ['employee_first_name', 'employee_email']
|
|
||||||
def extract_fields(params)
|
|
||||||
FieldExtractor.call(params)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Merges ATS prefill values with existing submitter values
|
|
||||||
#
|
|
||||||
# Existing submitter values always take precedence over ATS values to prevent
|
|
||||||
# overwriting user input.
|
|
||||||
#
|
|
||||||
# @param submitter_values [Hash] Existing values entered by submitters
|
|
||||||
# @param ats_values [Hash] Prefill values from ATS system
|
|
||||||
# @param template_fields [Array<Hash>, nil] Template field definitions
|
|
||||||
# @return [Hash] Merged values with submitter values taking precedence
|
|
||||||
#
|
|
||||||
# @example
|
|
||||||
# AtsPrefill.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, ats_values, template_fields = nil)
|
|
||||||
ValueMerger.call(submitter_values, ats_values, template_fields)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Finds field UUID by matching ATS field name to template field's prefill attribute
|
|
||||||
#
|
|
||||||
# @param field_name [String] ATS field name to look up
|
|
||||||
# @param template_fields [Array<Hash>, nil] Template field definitions
|
|
||||||
# @return [String, nil] Field UUID if found, nil otherwise
|
|
||||||
#
|
|
||||||
# @example
|
|
||||||
# AtsPrefill.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 ATS field names to field UUIDs
|
|
||||||
#
|
|
||||||
# @example
|
|
||||||
# AtsPrefill.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 ATS-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
|
|
||||||
@ -1,85 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module AtsPrefill
|
|
||||||
module CacheManager
|
|
||||||
# Cache TTL for ATS 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
|
|
||||||
@ -1,77 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module AtsPrefill
|
|
||||||
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 ATS prefill field names from Base64-encoded parameters
|
|
||||||
#
|
|
||||||
# This method decodes the ats_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 ATS field names, empty array if none found or on error
|
|
||||||
#
|
|
||||||
# @example
|
|
||||||
# # With params[:ats_fields] = Base64.urlsafe_encode64(['employee_first_name', 'employee_email'].to_json)
|
|
||||||
# AtsPrefill::FieldExtractor.call(params)
|
|
||||||
# # => ['employee_first_name', 'employee_email']
|
|
||||||
def call(params)
|
|
||||||
return [] if params[:ats_fields].blank?
|
|
||||||
|
|
||||||
cache_key = CacheManager.generate_cache_key('ats_fields', params[:ats_fields])
|
|
||||||
|
|
||||||
CacheManager.fetch_field_extraction(cache_key) do
|
|
||||||
extract_and_validate_fields(params[:ats_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 ATS 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_ats_field_name?(name) }
|
|
||||||
end
|
|
||||||
|
|
||||||
# Checks if a field name matches the valid ATS field pattern
|
|
||||||
#
|
|
||||||
# @param name [String] Field name to validate
|
|
||||||
# @return [Boolean] True if field name is valid, false otherwise
|
|
||||||
def valid_ats_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_ats_field_name?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,84 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module AtsPrefill
|
|
||||||
module FieldMapper
|
|
||||||
# Creates optimized mapping between ATS field names and template field UUIDs
|
|
||||||
#
|
|
||||||
# Creates a hash mapping ATS 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 ATS field names to field UUIDs
|
|
||||||
#
|
|
||||||
# @example
|
|
||||||
# template_fields = [
|
|
||||||
# { 'uuid' => 'field-1', 'prefill' => 'employee_first_name' },
|
|
||||||
# { 'uuid' => 'field-2', 'prefill' => 'employee_last_name' }
|
|
||||||
# ]
|
|
||||||
# AtsPrefill::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 ATS 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] ATS 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 ATS 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
|
|
||||||
@ -1,62 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module AtsPrefill
|
|
||||||
module ValueMerger
|
|
||||||
# Merges ATS prefill values with existing submitter values
|
|
||||||
#
|
|
||||||
# This method combines ATS-provided prefill values with values already entered by submitters.
|
|
||||||
# Existing submitter values always take precedence over ATS 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 ats_values [Hash] Prefill values from ATS system, keyed by ATS 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 ATS values
|
|
||||||
#
|
|
||||||
# @example
|
|
||||||
# submitter_values = { 'field-uuid-1' => 'John' }
|
|
||||||
# ats_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' }
|
|
||||||
# ]
|
|
||||||
#
|
|
||||||
# AtsPrefill::ValueMerger.call(submitter_values, ats_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, ats_values, template_fields = nil)
|
|
||||||
return submitter_values if ats_values.blank?
|
|
||||||
|
|
||||||
# Build optimized lookup cache for better performance with large field sets
|
|
||||||
field_mapping = FieldMapper.call(template_fields)
|
|
||||||
|
|
||||||
merge_values(submitter_values, ats_values, field_mapping)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
# Merges ATS values into submitter values for fields that are blank
|
|
||||||
#
|
|
||||||
# @param submitter_values [Hash] Current submitter field values
|
|
||||||
# @param ats_values [Hash] ATS field values to merge
|
|
||||||
# @param field_mapping [Hash] Mapping of ATS field names to template field UUIDs
|
|
||||||
# @return [Hash] Updated submitter values
|
|
||||||
def merge_values(submitter_values, ats_values, field_mapping)
|
|
||||||
return submitter_values if ats_values.blank? || field_mapping.blank?
|
|
||||||
|
|
||||||
ats_values.each do |ats_field_name, ats_value|
|
|
||||||
field_uuid = field_mapping[ats_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] = ats_value if current_value.nil? || current_value == ''
|
|
||||||
end
|
|
||||||
|
|
||||||
submitter_values
|
|
||||||
end
|
|
||||||
|
|
||||||
module_function :call, :merge_values
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,233 +0,0 @@
|
|||||||
# 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
|
|
||||||
{
|
|
||||||
ats_fields: Base64.urlsafe_encode64(%w[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
|
|
||||||
ats_fields_data = %w[employee_first_name employee_last_name employee_email]
|
|
||||||
ats_values_data = {
|
|
||||||
'employee_first_name' => 'John',
|
|
||||||
'employee_last_name' => 'Smith',
|
|
||||||
'employee_email' => 'john.smith@company.com'
|
|
||||||
}
|
|
||||||
|
|
||||||
encoded_fields = Base64.urlsafe_encode64(ats_fields_data.to_json)
|
|
||||||
encoded_values = Base64.urlsafe_encode64(ats_values_data.to_json)
|
|
||||||
|
|
||||||
params = ActionController::Parameters.new({
|
|
||||||
ats_fields: encoded_fields,
|
|
||||||
ats_values: encoded_values
|
|
||||||
})
|
|
||||||
|
|
||||||
allow(controller).to receive(:params).and_return(params)
|
|
||||||
|
|
||||||
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
|
|
||||||
@ -1,148 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe AtsPrefill::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
|
|
||||||
@ -1,191 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe AtsPrefill::FieldExtractor do
|
|
||||||
describe '.call' do
|
|
||||||
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) { ActionController::Parameters.new(ats_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 = AtsPrefill::CacheManager.generate_cache_key('ats_fields', encoded_fields)
|
|
||||||
|
|
||||||
allow(AtsPrefill::CacheManager).to receive(:fetch_field_extraction).and_call_original
|
|
||||||
|
|
||||||
described_class.call(params)
|
|
||||||
|
|
||||||
expect(AtsPrefill::CacheManager).to have_received(:fetch_field_extraction).with(cache_key)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns cached result on subsequent calls' do
|
|
||||||
cache_key = AtsPrefill::CacheManager.generate_cache_key('ats_fields', encoded_fields)
|
|
||||||
cached_result = ['cached_field']
|
|
||||||
|
|
||||||
allow(AtsPrefill::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 ats_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 ats_fields parameter is blank' do
|
|
||||||
let(:params) { ActionController::Parameters.new(ats_fields: '') }
|
|
||||||
|
|
||||||
it 'returns an empty array' do
|
|
||||||
result = described_class.call(params)
|
|
||||||
expect(result).to eq([])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when ats_fields parameter is invalid' do
|
|
||||||
let(:params) { ActionController::Parameters.new(ats_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(ats_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(ats_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(ats_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(ats_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(ats_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(ats_fields: invalid_json)
|
|
||||||
|
|
||||||
result = described_class.call(params)
|
|
||||||
expect(result).to eq([])
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'handles Base64 decoding errors gracefully' do
|
|
||||||
params = ActionController::Parameters.new(ats_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
|
|
||||||
@ -1,251 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe AtsPrefill::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 = AtsPrefill::CacheManager.generate_cache_key('field_mapping', cache_signature)
|
|
||||||
|
|
||||||
allow(AtsPrefill::CacheManager).to receive(:fetch_field_mapping).and_call_original
|
|
||||||
|
|
||||||
described_class.call(template_fields)
|
|
||||||
|
|
||||||
expect(AtsPrefill::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 = AtsPrefill::CacheManager.generate_cache_key('field_mapping', cache_signature)
|
|
||||||
cached_result = { 'cached_field' => 'cached_uuid' }
|
|
||||||
|
|
||||||
allow(AtsPrefill::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
|
|
||||||
@ -1,213 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe AtsPrefill::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(:ats_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, ats_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, ats_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
|
|
||||||
ats_values_with_unknown = ats_values.merge('unknown_field' => 'Unknown Value')
|
|
||||||
|
|
||||||
result = described_class.call(submitter_values, ats_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(AtsPrefill::FieldMapper).to receive(:call).and_return(expected_mapping)
|
|
||||||
|
|
||||||
described_class.call(submitter_values, ats_values, template_fields)
|
|
||||||
|
|
||||||
expect(AtsPrefill::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, ats_values, nil)
|
|
||||||
expect(result).to eq(submitter_values)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when ats_values is blank' do
|
|
||||||
it 'returns original submitter_values for nil ats_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 ats_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, ats_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' => '' }
|
|
||||||
ats_values = { 'employee_first_name' => 'John' }
|
|
||||||
|
|
||||||
result = described_class.call(submitter_values, ats_values, template_fields)
|
|
||||||
|
|
||||||
expect(result['field-1-uuid']).to eq('John')
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'treats nil as blank' do
|
|
||||||
submitter_values = { 'field-1-uuid' => nil }
|
|
||||||
ats_values = { 'employee_first_name' => 'John' }
|
|
||||||
|
|
||||||
result = described_class.call(submitter_values, ats_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 }
|
|
||||||
ats_values = { 'employee_first_name' => 'John' }
|
|
||||||
|
|
||||||
result = described_class.call(submitter_values, ats_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 }
|
|
||||||
ats_values = { 'employee_first_name' => 'John' }
|
|
||||||
|
|
||||||
result = described_class.call(submitter_values, ats_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(AtsPrefill::FieldMapper).to receive(:call).and_return({})
|
|
||||||
|
|
||||||
result = described_class.call(submitter_values, ats_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'
|
|
||||||
}
|
|
||||||
|
|
||||||
ats_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, ats_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, ats_values, template_fields)
|
|
||||||
|
|
||||||
expect(result).to be(submitter_values)
|
|
||||||
expect(submitter_values).not_to eq(original_values)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,175 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe AtsPrefill 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(ats_fields: 'encoded_data') }
|
|
||||||
|
|
||||||
it 'delegates to FieldExtractor' do
|
|
||||||
expected_result = %w[employee_first_name employee_last_name]
|
|
||||||
|
|
||||||
allow(AtsPrefill::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(:ats_values) { { 'employee_last_name' => 'Doe' } }
|
|
||||||
|
|
||||||
it 'delegates to ValueMerger' do
|
|
||||||
expected_result = { 'field-1-uuid' => 'John', 'field-2-uuid' => 'Doe' }
|
|
||||||
|
|
||||||
allow(AtsPrefill::ValueMerger).to receive(:call).with(submitter_values, ats_values,
|
|
||||||
template_fields).and_return(expected_result)
|
|
||||||
|
|
||||||
result = described_class.merge_values(submitter_values, ats_values, template_fields)
|
|
||||||
expect(result).to eq(expected_result)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'handles nil template_fields' do
|
|
||||||
allow(AtsPrefill::ValueMerger).to receive(:call).with(submitter_values, ats_values,
|
|
||||||
nil).and_return(submitter_values)
|
|
||||||
|
|
||||||
result = described_class.merge_values(submitter_values, ats_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(AtsPrefill::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(AtsPrefill::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(AtsPrefill::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(AtsPrefill::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(ats_fields: encoded_fields)
|
|
||||||
end
|
|
||||||
|
|
||||||
let(:submitter_values) { { 'field-1-uuid' => 'Existing Name' } }
|
|
||||||
let(:ats_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, ats_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 ATS
|
|
||||||
})
|
|
||||||
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(AtsPrefill::FieldExtractor).to receive(:call).and_raise(StandardError, 'Test error')
|
|
||||||
|
|
||||||
expect { described_class.extract_fields({}) }.to raise_error(StandardError, 'Test error')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
Loading…
Reference in new issue