diff --git a/lib/aca_entities/async_api/fdsh_gateway/http_cms.yml b/lib/aca_entities/async_api/fdsh_gateway/http_cms.yml index 116fb9a6b..53b6eb67d 100644 --- a/lib/aca_entities/async_api/fdsh_gateway/http_cms.yml +++ b/lib/aca_entities/async_api/fdsh_gateway/http_cms.yml @@ -415,6 +415,33 @@ channels: headers: Content-Type: application/json Accept: application/json + /RIDPCrossCoreService: + publish: + operationId: /RIDPCrossCoreService + description: Send RIDP primary JSON request to CMS + message: + contentType: application/json + bindings: + http: + type: request + method: POST + contentType: application/json + headers: + Content-Type: application/json + Accept: application/json + subscribe: + operationId: /on/RIDPCrossCoreService + description: Receive RIDP primary JSON response from CMS + message: + contentType: application/json + bindings: + http: + type: response + method: GET + contentType: application/json + headers: + Content-Type: application/json + Accept: application/json tags: - name: linter_tag description: placeholder that satisfies the linter diff --git a/lib/aca_entities/fdsh/ridp.rb b/lib/aca_entities/fdsh/ridp.rb index 6ae8c5367..7f00e1a02 100644 --- a/lib/aca_entities/fdsh/ridp.rb +++ b/lib/aca_entities/fdsh/ridp.rb @@ -63,6 +63,10 @@ # operations require_relative 'ridp/h139/operations/generate_primary_request_payload' +require_relative 'ridp/rj139/operations/person_to_primary_request' +require_relative 'ridp/rj139/operations/cms_primary_response_to_cv3_primary_response' +require_relative 'ridp/rj139/operations/cms_secondary_response_to_cv3_secondary_response' +require_relative 'ridp/rj139/operations/evidence_to_secondary_request' # happymapper require 'aca_entities/serializers/xml/fdsh/ridp' diff --git a/lib/aca_entities/fdsh/ridp/rj139/operations/cms_primary_response_to_cv3_primary_response.rb b/lib/aca_entities/fdsh/ridp/rj139/operations/cms_primary_response_to_cv3_primary_response.rb new file mode 100644 index 000000000..c1125deda --- /dev/null +++ b/lib/aca_entities/fdsh/ridp/rj139/operations/cms_primary_response_to_cv3_primary_response.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +require 'json' +require "json-schema" + +module AcaEntities + module Fdsh + module Ridp + module Rj139 + module Operations + # convert cms ridp primary response payload into a cv3 primary request + class CmsPrimaryResponseToCv3PrimaryResponse + include Dry::Monads[:result, :do, :try] + + def call(params) + payload = yield validate_payload(params) + response = yield construct_primary_response(payload) + validate_primary_response(response) + end + + private + + def validate_payload(payload) + schema_data = JSON.parse(File.read(Pathname.pwd.join("lib/aca_entities/fdsh/ridp/rj139/schemas/RIDP-Response-schema.json"))) + + result = begin + JSON::Validator.fully_validate(schema_data, JSON.parse(payload.to_json)) + rescue JSON::Schema::ValidationError => e + e.message + end + result.empty? ? Success(payload) : Failure(result.to_s) + end + + def construct_primary_response(payload) + parsed_paylod = JSON.parse(payload.to_json) + ridp = parsed_paylod["ridpResponse"] + result_hash = { + Response: { + ResponseMetadata: { + ResponseCode: ridp["responseMetadata"]["responseCode"], + ResponseDescriptionText: ridp["responseMetadata"]["responseText"], + TDSResponseDescriptionText: ridp["responseMetadata"]["tdsResponseText"] + }, + VerificationResponse: { + SessionIdentification: ridp["sessionIdentification"], + DSHReferenceNumber: ridp["hubReferenceNumber"], + FinalDecisionCode: ridp["finalDecisionCode"], + VerificationQuestions: get_question_set(ridp) + } + } + } + + Success(result_hash) + end + + def get_question_set(ridp) + return unless ridp.keys.include?("verificationQuestionArray") + + verification_question_set = [] + + ridp["verificationQuestionArray"].each do |question_set| + mapped_array = { + VerificationQuestionText: question_set["verificationQuestionSet"]["verificationQuestionText"], + VerificationAnswerChoiceText: question_set["verificationQuestionSet"]["verificationAnswerChoiceArray"].map do |c| + c["verificationAnswerChoiceText"] + end + } + verification_question_set << mapped_array + end + + { VerificationQuestionSet: verification_question_set } + end + + def validate_primary_response(payload) + result = ::AcaEntities::Fdsh::Ridp::H139::PrimaryResponseContract.new.call(payload) + + if result.success? + Success(payload) + else + Failure("Invalid response, #{result.errors.to_h}") + end + end + + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/aca_entities/fdsh/ridp/rj139/operations/cms_secondary_response_to_cv3_secondary_response.rb b/lib/aca_entities/fdsh/ridp/rj139/operations/cms_secondary_response_to_cv3_secondary_response.rb new file mode 100644 index 000000000..9b0c7a0a6 --- /dev/null +++ b/lib/aca_entities/fdsh/ridp/rj139/operations/cms_secondary_response_to_cv3_secondary_response.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +require 'json' +require "json-schema" + +module AcaEntities + module Fdsh + module Ridp + module Rj139 + module Operations + # convert cms ridp secondary response payload into a cv3 secondary request + class CmsSecondaryResponseToCv3SecondaryResponse + include Dry::Monads[:result, :do, :try] + + def call(params) + payload = yield validate_payload(params) + response = yield construct_secondary_response(payload) + validate_secondary_response(response) + end + + private + + def validate_payload(payload) + schema_data = JSON.parse(File.read(Pathname.pwd.join("lib/aca_entities/fdsh/ridp/rj139/schemas/RIDP-Response-schema.json"))) + + result = begin + JSON::Validator.fully_validate(schema_data, JSON.parse(payload.to_json)) + rescue JSON::Schema::ValidationError => e + e.message + end + result.empty? ? Success(payload) : Failure(result.to_s) + end + + def construct_secondary_response(payload) + parsed_paylod = JSON.parse(payload.to_json) + ridp = parsed_paylod["ridpResponse"] + result_hash = { + Response: { + ResponseMetadata: { + ResponseCode: ridp["responseMetadata"]["responseCode"], + ResponseDescriptionText: ridp["responseMetadata"]["responseText"], + TDSResponseDescriptionText: ridp["responseMetadata"]["tdsResponseText"] + }, + VerificationResponse: { + SessionIdentification: ridp["sessionIdentification"], + DSHReferenceNumber: ridp["hubReferenceNumber"], + FinalDecisionCode: ridp["finalDecisionCode"] + } + } + } + Success(result_hash) + end + + def validate_secondary_response(payload) + result = ::AcaEntities::Fdsh::Ridp::H139::SecondaryResponseContract.new.call(payload) + + if result.success? + Success(payload) + else + Failure("Invalid response, #{result.errors.to_h}") + end + end + + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/aca_entities/fdsh/ridp/rj139/operations/evidence_to_secondary_request.rb b/lib/aca_entities/fdsh/ridp/rj139/operations/evidence_to_secondary_request.rb new file mode 100644 index 000000000..86ea8e5e4 --- /dev/null +++ b/lib/aca_entities/fdsh/ridp/rj139/operations/evidence_to_secondary_request.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require 'json' +require "json-schema" + +module AcaEntities + module Fdsh + module Ridp + module Rj139 + module Operations + # convert evidence entity to ridp secondary request + class EvidenceToSecondaryRequest + include Dry::Monads[:result, :do, :try] + + def call(params) + payload = yield construct_initial_request(params) + validate_payload(payload) + end + + private + + def construct_initial_request(payload) + request = { ridpRequest: { secondaryRequest: construct_primary_request(payload) } } + Success(request) + end + + def construct_primary_request(payload) + { + verificationAnswerArray: construct_answer_array(payload[:VerificationAnswerSet]), + sessionIdentification: payload[:SessionIdentification], + hubReferenceNumber: payload[:transmission_id] + } + end + + def construct_answer_array(answers) + answers[:VerificationAnswers].collect do |answer| + { verificationAnswerSet: { verificationAnswer: answer[:VerificatonAnswer]&.to_s, + verificationQuestionNumber: answer[:VerificationQuestionNumber]&.to_s } } + end + end + + def validate_payload(payload) + schema_data = JSON.parse(File.read(Pathname.pwd.join("lib/aca_entities/fdsh/ridp/rj139/schemas/RIDP-Request-schema.json"))) + + # CMS requested no blank data be sent in the request + payload[:ridpRequest][:secondaryRequest].delete_if { |_k, v| v.blank? } + + result = begin + JSON::Validator.fully_validate(schema_data, JSON.parse(payload.to_json)) + rescue JSON::Schema::ValidationError => e + e.message + end + result.empty? ? Success(payload.to_json) : Failure(result.to_s) + end + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/aca_entities/fdsh/ridp/rj139/operations/person_to_primary_request.rb b/lib/aca_entities/fdsh/ridp/rj139/operations/person_to_primary_request.rb new file mode 100644 index 000000000..788a5be98 --- /dev/null +++ b/lib/aca_entities/fdsh/ridp/rj139/operations/person_to_primary_request.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +require 'json' +require "json-schema" + +module AcaEntities + module Fdsh + module Ridp + module Rj139 + module Operations + # convert person entity to ridp primary request + class PersonToPrimaryRequest + include Dry::Monads[:result, :do, :try] + + def call(params) + payload = yield construct_initial_request(params) + validate_payload(payload) + end + + private + + def construct_initial_request(payload) + request = { ridpRequest: { primaryRequest: construct_primary_request(payload) } } + Success(request) + end + + def construct_primary_request(payload) + { + person: construct_person_demographics(payload), + contactInformation: construct_contact_information(payload) + } + end + + def construct_person_demographics(payload) + { + personSurName: payload.person_name.last_name&.gsub(/[^A-Za-z]/, ''), + personMiddleName: payload.person_name.middle_name&.gsub(/[^A-Za-z]/, ''), + personGivenName: payload.person_name.first_name&.gsub(/[^A-Za-z]/, ''), + personBirthDate: construct_birth_date(payload), + personSuffixName: payload.person_name.name_sfx&.gsub(/[^A-Za-z]/, ''), + personSocialSecurityNumber: decrypt_ssn(payload.person_demographics.encrypted_ssn), + personPreferredLanguage: construct_language_code(payload.person_demographics&.language_code) + } + end + + def construct_language_code(language_preference) + lan_mapper = { 'en' => 'eng', 'es' => 'spa' } + return 'eng' unless lan_mapper.keys.include?(language_preference) + lan_mapper[language_preference] + end + + def construct_birth_date(person) + person&.person_demographics&.dob + end + + def decrypt_ssn(encrypted_ssn) + return nil if encrypted_ssn.blank? + + AcaEntities::Operations::Encryption::Decrypt.new.call({ value: encrypted_ssn }).value! + end + + def construct_contact_information(payload) + home_address = payload.home_address || payload.addresses&.last + home_phone = payload.phones ? (payload.home_phone || payload.phones&.last) : nil + { + streetName: home_address&.address_1&.gsub(/[^0-9A-Za-z\s]/, ''), + cityName: home_address&.city&.gsub(/[^0-9A-Za-z]/, ''), + usStateCode: home_address&.state, + zipCode: home_address&.zip, + telephoneNumber: home_phone&.full_phone_number + } + end + + def validate_payload(payload) + schema_data = JSON.parse(File.read(Pathname.pwd.join("lib/aca_entities/fdsh/ridp/rj139/schemas/RIDP-Request-schema.json"))) + + # CMS requested no blank data be sent in the request + payload[:ridpRequest][:primaryRequest][:person].delete_if { |_k, v| v.blank? } + payload[:ridpRequest][:primaryRequest][:contactInformation].delete_if { |_k, v| v.blank? } + + result = begin + JSON::Validator.fully_validate(schema_data, JSON.parse(payload.to_json)) + rescue JSON::Schema::ValidationError => e + e.message + end + result.empty? ? Success(payload.to_json) : Failure(result.to_s) + end + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/aca_entities/fdsh/ridp/rj139/schemas/RIDP-Request-schema.json b/lib/aca_entities/fdsh/ridp/rj139/schemas/RIDP-Request-schema.json new file mode 100644 index 000000000..eaa32b4c1 --- /dev/null +++ b/lib/aca_entities/fdsh/ridp/rj139/schemas/RIDP-Request-schema.json @@ -0,0 +1,298 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "id": "./RIDP-Request-schema.json", + "title": "Remote Identity Proofing (RIDP) (RJ139) - Request Schema", + "description": "The RIDP service allows for identity proofing an Applicant seeking to register with the Marketplace.", + "type": "object", + "$comment": "Define root as ridpRequest", + "required": ["ridpRequest"], + "additionalProperties": false, + "properties": { + "ridpRequest": { + "type": "object", + "additionalProperties": false, + "properties": { + "primaryRequest": {"$ref": "#/definitions/primaryRequestType"}, + "secondaryRequest": {"$ref": "#/definitions/secondaryRequestType"} + } + } + }, + "definitions": { + "primaryRequestType": { + "description": "RIDP Primary Request. Part of a mutex with RIDP Secondary Request.", + "type": "object", + "required": [ + "person", + "contactInformation" + ], + "additionalProperties": false, + "properties": { + "person": {"$ref": "#/definitions/personType"}, + "contactInformation": {"$ref": "#/definitions/contactInformationType"}, + "levelOfProofingCode": { + "description": "Identifies level of questions Requester chose for individual being identity proofed.", + "type": "string", + "enum": [ + "LevelTwo", + "LevelThree", + "OptionThree" + ] + }, + "subscriberNumber": { + "description": "The subscriber number (Sub Code) of the Requester.", + "type": "string", + "minLength": 7, + "maxLength": 7, + "pattern": "^([0-9]{7})$" + } + } + }, + "personType": { + "description": "Container for personal information about an individual", + "type": "object", + "required": [ + "personGivenName", + "personSurName" + ], + "additionalProperties": false, + "properties": { + "personBirthDate": { + "description": "Date of birth of a person; YYYY-MM-DD", + "type": "string", + "pattern": "((((19|2[0-9])([2468][048]|[13579][26]|0[48])|2[048]00)-02-29|((19|2[0-9])[0-9]{2}-(0[469]|11)-(0[1-9]|[12][0-9]|30)|(19|2[0-9])[0-9]{2}-(0[13578]|1[02])-(0[1-9]|[12][0-9]|3[01])|(19|2[0-9])[0-9]{2}-02-(0[1-9]|1[0-9]|2[0-8]))))" + }, + "personGivenName": { + "description": "First name of person", + "type": "string", + "minLength": 1, + "maxLength": 32, + "pattern": "^([a-zA-Z]{1,32})$" + }, + "personMiddleName": { + "description": "Middle name of person", + "type": "string", + "minLength": 1, + "maxLength": 32, + "pattern": "^([a-zA-Z]{1,32})$" + }, + "personSurName": { + "description": "Last name of person", + "type": "string", + "minLength": 1, + "maxLength": 32, + "pattern": "^([a-zA-Z \\-'\\s]*\\S[a-zA-Z \\-'\\s]*)$" + }, + "personSuffixName": { + "description": "Suffix for a person's name", + "type": "string", + "maxLength": 3 + }, + "personSocialSecurityNumber": { + "description": "A person's Social Security Number", + "type": "string", + "pattern": "^(\\d{3}-\\d{2}-\\d{4}|\\d{9}|\\d{4})$" + }, + "personPreferredLanguage": { + "description": "A capacity of a person for a language with which that person has the strongest preference. eng - English; spa - Spanish.", + "type": "string", + "enum": [ + "spa", + "eng" + ] + } + } + }, + "contactInformationType": { + "description": "Container for contact information about individual being proofed.", + "type": "object", + "required": [ + "streetName", + "cityName", + "usStateCode", + "zipCode" + ], + "additionalProperties": false, + "properties": { + "streetName": { + "description": "A road, thoroughfare or highway.", + "type": "string", + "minLength": 1, + "maxLength": 60, + "pattern": "^([a-zA-Z0-9# \\- / \\.]{1,60})$" + }, + "cityName": { + "description": "A name of a city or town.", + "type": "string", + "minLength": 1, + "maxLength": 40, + "pattern": "^([a-zA-Z \\.]{1,40})$" + }, + "zipCode": { + "description": "An identifier of a post office-assigned zone for an address.", + "type": "string", + "minLength": 5, + "maxLength": 5, + "pattern": "^([\\d]{5})$" + }, + "zipCodeExtension": { + "description": "An identifier of a smaller area within a post office-assigned zone for an address.", + "type": "string", + "minLength": 4, + "maxLength": 4, + "pattern": "^([\\d]{4})$" + }, + "telephoneNumber": { + "description": "A telephone number..", + "type": "string", + "minLength": 10, + "maxLength": 13, + "pattern": "^([a-zA-Z0-9\\(\\)\\-]{10,13})$" + }, + "usStateCode": { + "type": "string", + "description": "A state, commonwealth, province, or other such geopolitical subdivision of the United States of America. AA - Armed Forces Americas (except Canada). AE - Armed Forces Africa, Canada, Europe, Middle East. AK - ALASKA. AL - ALABAMA. AP - Armed Forces Pacific. AR - ARKANSAS. AS - AMERICAN SAMOA. AZ - ARIZONA . CA - CALIFORNIA. CO - COLORADO. CT - CONNECTICUT. DC - DISTRICT OF COLUMBIA. DE - DELAWARE. FL - FLORIDA. FM - FEDERATED STATES OF MICRONESIA. GA - GEORGIA. GU - GUAM. HI - HAWAII. IA - IOWA. ID - IDAHO. IL - ILLINOIS. IN - INDIANA. KS - KANSAS. KY - KENTUCKY. LA - LOUISIANA. MA - MASSACHUSETTS. MD - MARYLAND. ME - MAINE. MH - MARSHALL ISLANDS. MI - MICHIGAN. MN - MINNESOTA. MO - MISSOURI. MP - NORTHERN MARIANA ISLANDS. MS - MISSISSIPPI. MT - MONTANA. NC - NORTH CAROLINA. ND - NORTH DAKOTA. NE - NEBRASKA. NH - NEW HAMPSHIRE. NJ - NEW JERSEY. NM - NEW MEXICO. NV - NEVADA. NY - NEW YORK. OH - OHIO. OK - OKLAHOMA. OR - OREGON. PA - PENNSYLVANIA. PR - PUERTO RICO. PW - PALAU. RI - RHODE ISLAND. SC - SOUTH CAROLINA. SD - SOUTH DAKOTA. TN - TENNESSEE. TX - TEXAS. UT - UTAH. VA - VIRGINIA . VI - VIRGIN ISLANDS. VT - VERMONT. WA - WASHINGTON. WI - WISCONSIN. WV - WEST VIRGINIA. WY - WYOMING. ", + "enum": [ + "AA", + "AE", + "AK", + "AL", + "AP", + "AR", + "AS", + "AZ", + "CA", + "CO", + "CT", + "DC", + "DE", + "FL", + "FM", + "GA", + "GU", + "HI", + "IA", + "ID", + "IL", + "IN", + "KS", + "KY", + "LA", + "MA", + "MD", + "ME", + "MH", + "MI", + "MN", + "MO", + "MP", + "MS", + "MT", + "NC", + "ND", + "NE", + "NH", + "NJ", + "NM", + "NV", + "NY", + "OH", + "OK", + "OR", + "PA", + "PR", + "PW", + "RI", + "SC", + "SD", + "TN", + "TX", + "UT", + "VA", + "VI", + "VT", + "WA", + "WI", + "WV", + "WY" + ] + } + } + }, + "secondaryRequestType": { + "description": "RIDP Secondary Request. Part of a mutex with RIDP Primary Request.", + "type": "object", + "required": [ + "verificationAnswerArray", + "sessionIdentification", + "hubReferenceNumber" + ], + "additionalProperties": false, + "properties": { + "verificationAnswerArray": { + "type": "array", + "minItems": 3, + "maxItems": 5, + "items": {"$ref": "#/definitions/verificationAnswerArrayType"} + }, + "sessionIdentification": { + "description": "Session ID used to tie subsequent submissions to the initial inquiry.", + "type": "string", + "minLength": 1, + "maxLength": 70, + "pattern": "^([a-zA-Z0-9# \\- / \\.]{1,70})$" + }, + "hubReferenceNumber": { + "description": "A unique number generated by the Hub to identify a Remote Identity proofing request.", + "type": "string", + "minLength": 1, + "maxLength": 90 + } + } + }, + "verificationAnswerArrayType": { + "description": "A collection of questions and answers submitted to the Hub", + "type": "object", + "required": ["verificationAnswerSet"], + "additionalProperties": false, + "properties": { + "verificationAnswerSet": { + "required": ["verificationAnswer", "verificationQuestionNumber"], + "properties": { + "verificationAnswer": { + "description": "Answers to individual questions.", + "type": "string", + "enum": [ + "1", + "01", + "2", + "02", + "3", + "03", + "4", + "04", + "5", + "05" + ] + }, + "verificationQuestionNumber": { + "description": "Question number of the corresponding answer.", + "type": "string", + "enum": [ + "1", + "01", + "2", + "02", + "3", + "03", + "4", + "04", + "5", + "05" + ] + } + } + } + } + } + } +} \ No newline at end of file diff --git a/lib/aca_entities/fdsh/ridp/rj139/schemas/RIDP-Response-schema.json b/lib/aca_entities/fdsh/ridp/rj139/schemas/RIDP-Response-schema.json new file mode 100644 index 000000000..2ac6033f3 --- /dev/null +++ b/lib/aca_entities/fdsh/ridp/rj139/schemas/RIDP-Response-schema.json @@ -0,0 +1,118 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "id": "./RIDP-Response-schema.json", + "title": "Remote Identity Proofing (RIDP) (RJ139) - Response Schema", + "description": "The RIDP service allows for identity proofing an Applicant seeking to register with the Marketplace.", + "type": "object", + "$comment": "Define root as ridpResponse", + "required": ["ridpResponse"], + "additionalProperties": false, + "properties": { + "ridpResponse": { + "type": "object", + "required": ["responseMetadata"], + "additionalProperties": false, + "properties": { + "sessionIdentification": { + "description": "Session ID used to tie subsequent submissions to the initial inquiry.", + "type": "string", + "minLength": 1, + "maxLength": 70, + "pattern": "^([a-zA-Z0-9# \\- / \\.]{1,70})$" + }, + "verificationQuestionArray": { + "type": "array", + "minItems": 3, + "maxItems": 5, + "items": {"$ref": "#/definitions/verificationQuestionArrayType"} + }, + "finalDecisionCode": { + "description": "Enumerated values for Verification Decision Codes: ACC - Accept; RF1 - Prompt to Experian Call Center; RF2 - Back to CMS to prompt user for additional information OR recheck of data inputted; RF3 - Use Limit Violation / Prompt to Experian Call Center; RF4 - Back to CMS / HARD FAIL / Do NOT Prompt to Experian Call Center.", + "type": "string", + "enum": [ + "ACC", + "RF1", + "RF2", + "RF3", + "RF4" + ] + }, + "hubReferenceNumber": { + "description": "A unique number generated by the Hub to identify a Remote Identity proofing request.", + "type": "string", + "minLength": 1, + "maxLength": 90 + }, + "responseMetadata": {"$ref": "#/definitions/responseMetadataType"} + } + } + }, + "definitions": { + "verificationQuestionArrayType": { + "description": "Container for the sets of verification questions and answers.", + "type": "object", + "required": ["verificationQuestionSet"], + "additionalProperties": false, + "properties": { + "verificationQuestionSet": { + "description": "Container for the QuestionText and AnswerChoice array.", + "type": "object", + "required": [ + "verificationQuestionText", + "verificationAnswerChoiceArray" + ], + "additionalProperties": false, + "properties": { + "verificationQuestionText": { + "description": "The question in text form.", + "type": "string" + }, + "verificationAnswerChoiceArray": { + "type": "array", + "minItems": 1, + "maxItems": 5, + "items": {"$ref": "#/definitions/verificationAnswerChoiceArrayType"} + } + } + } + } + }, + "verificationAnswerChoiceArrayType": { + "description": "An array of answers.", + "type": "object", + "required": ["verificationAnswerChoiceText"], + "additionalProperties": false, + "properties": { + "verificationAnswerChoiceText": { + "description": "The answer in text form.", + "type": "string" + } + } + }, + "responseMetadataType": { + "description": "A set of details about a response (successful or not)", + "type": "object", + "required": [ + "responseCode", + "responseText" + ], + "additionalProperties": false, + "properties": { + "responseCode": { + "description": "A coded response; expecting HE/HS/HX followed by 6 digits but not enforced beyond 8 characters.", + "type": "string", + "minLength": 8, + "maxLength": 8 + }, + "responseText": { + "description": "A description of a response", + "type": "string" + }, + "tdsResponseText": { + "description": "A description of a response from a data source designated as trusted (a trusted data source or TDS) by the Centers for Medicare and Medicaid Services.", + "type": "string" + } + } + } + } +} \ No newline at end of file diff --git a/spec/aca_entities/fdsh/operations/ridp/rj139/cms_primary_response_to_cv3_primary_response_spec.rb b/spec/aca_entities/fdsh/operations/ridp/rj139/cms_primary_response_to_cv3_primary_response_spec.rb new file mode 100644 index 000000000..38c620e4b --- /dev/null +++ b/spec/aca_entities/fdsh/operations/ridp/rj139/cms_primary_response_to_cv3_primary_response_spec.rb @@ -0,0 +1,98 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ::AcaEntities::Fdsh::Ridp::Rj139::Operations::CmsPrimaryResponseToCv3PrimaryResponse, dbclean: :after_each do + + describe 'cms response' do + let(:input_params) do + { + ridpResponse: { + responseMetadata: { + responseCode: "ABCDEFGH", + responseText: "ABCDEFGHIJKLMNOPQRSTUVWXYZA", + tdsResponseText: "ABCDEFGHIJKLMNOPQRSTU" + }, + sessionIdentification: "# Ik5/ 77RnhJ", + verificationQuestionArray: [ + { + verificationQuestionSet: { + verificationQuestionText: "ABCDEFG", + verificationAnswerChoiceArray: [ + { + verificationAnswerChoiceText: "ABCDEFGHIJKLMNOPQRSTUVWXYZABC" + } + ] + } + }, + { + verificationQuestionSet: { + verificationQuestionText: "ABCDEFGHIJKLMN", + verificationAnswerChoiceArray: [ + { + verificationAnswerChoiceText: "ABCDEFGHIJKLMNOPQ" + } + ] + } + }, + { + verificationQuestionSet: { + verificationQuestionText: "ABCD", + verificationAnswerChoiceArray: [ + { + verificationAnswerChoiceText: "ABCDEFGHIJKLM" + } + ] + } + }, + { + verificationQuestionSet: { + verificationQuestionText: "ABCDEFGHIJKLMNOP", + verificationAnswerChoiceArray: [ + { + verificationAnswerChoiceText: "ABCDEFGHIJKLMNOPQRSTUVWXYZAB" + } + ] + } + }, + { + verificationQuestionSet: { + verificationQuestionText: "ABCDEFGHIJKLMNOPQRSTUVWXYZA", + verificationAnswerChoiceArray: [ + { + verificationAnswerChoiceText: "ABCDEFG" + } + ] + } + } + ], + finalDecisionCode: "ACC", + hubReferenceNumber: "ABCDEFGH" + } + } + end + + context 'with valid response' do + before do + @result = subject.call(input_params) + end + + it 'should return success' do + expect(@result).to be_success + end + end + + context 'with invalid response' do + + before do + input_params[:ridpResponse].delete(:responseMetadata) + @result = subject.call(input_params) + end + + it 'should return failure' do + expect(@result).to be_failure + end + + end + end +end diff --git a/spec/aca_entities/fdsh/operations/ridp/rj139/cms_secondary_response_to_cv3_secondary_response_spec.rb b/spec/aca_entities/fdsh/operations/ridp/rj139/cms_secondary_response_to_cv3_secondary_response_spec.rb new file mode 100644 index 000000000..1d457787c --- /dev/null +++ b/spec/aca_entities/fdsh/operations/ridp/rj139/cms_secondary_response_to_cv3_secondary_response_spec.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ::AcaEntities::Fdsh::Ridp::Rj139::Operations::CmsSecondaryResponseToCv3SecondaryResponse, dbclean: :after_each do + + describe 'cms response' do + let(:input_params) do + { + ridpResponse: { + responseMetadata: { + responseCode: "ABCDEFGH", + responseText: "ABCDEFGHIJKLMNOPQRSTUVWXYZA", + tdsResponseText: "ABCDEFGHIJKLMNOPQRSTU" + }, + sessionIdentification: "# Ik5/ 77RnhJ", + finalDecisionCode: "ACC", + hubReferenceNumber: "ABCDEFGH" + } + } + end + + context 'with valid response' do + before do + @result = subject.call(input_params) + end + + it 'should return success' do + expect(@result).to be_success + end + end + + context 'with invalid response' do + + before do + input_params[:ridpResponse].delete(:responseMetadata) + @result = subject.call(input_params) + end + + it 'should return failure' do + expect(@result).to be_failure + end + + end + end +end diff --git a/spec/aca_entities/fdsh/operations/ridp/rj139/evidence_to_secondary_request_spec.rb b/spec/aca_entities/fdsh/operations/ridp/rj139/evidence_to_secondary_request_spec.rb new file mode 100644 index 000000000..951c0cfdc --- /dev/null +++ b/spec/aca_entities/fdsh/operations/ridp/rj139/evidence_to_secondary_request_spec.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ::AcaEntities::Fdsh::Ridp::Rj139::Operations::EvidenceToSecondaryRequest, dbclean: :after_each do + + describe 'with valid arguments' do + context 'with valid payload' do + let!(:payload) do + { + :SessionIdentification => "347567asghfjgshfg", + :VerificationAnswerSet => + { :VerificationAnswers => + [ + { :VerificationQuestionNumber => 1, :VerificatonAnswer => 1 }, + { :VerificationQuestionNumber => 2, :VerificatonAnswer => 1 }, + { :VerificationQuestionNumber => 3, :VerificatonAnswer => 2 } + ] }, + :transmission_id => "test" + } + end + + before do + @result = subject.call(payload) + @json_result = JSON.parse(@result.success) + end + + it 'should return success' do + expect(@result).to be_success + end + + end + + context 'with invalid payload' do + let!(:payload) do + { + :SessionIdentification => "347567asghfjgshfg", + :VerificationAnswerSet => + { :VerificationAnswers => + [ + { :VerificationQuestionNumber => 1, :VerificatonAnswer => 1 }, + { :VerificationQuestionNumber => 2, :VerificatonAnswer => 1 }, + { :VerificationQuestionNumber => 3, :VerificatonAnswer => 2 } + ] } + } + end + + before do + @result = subject.call(payload) + end + + it 'should return failure' do + expect(@result).to be_failure + end + + end + end +end diff --git a/spec/aca_entities/fdsh/operations/ridp/rj139/person_to_primary_request_spec.rb b/spec/aca_entities/fdsh/operations/ridp/rj139/person_to_primary_request_spec.rb new file mode 100644 index 000000000..84a21ceb9 --- /dev/null +++ b/spec/aca_entities/fdsh/operations/ridp/rj139/person_to_primary_request_spec.rb @@ -0,0 +1,421 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ::AcaEntities::Fdsh::Ridp::Rj139::Operations::PersonToPrimaryRequest, dbclean: :after_each do + + describe 'with valid arguments' do + let!(:person_name) do + { + first_name: 'first name', + middle_name: 'middle name', + last_name: 'last name' + } + end + + let!(:person_health) do + { + is_tobacco_user: 'unknown', + is_physically_disabled: false + } + end + + let!(:person_reference) do + { + hbx_id: '1234', + first_name: 'first name', + middle_name: 'middle name', + last_name: 'last name', + dob: Date.today, + gender: 'male', + ssn: nil + } + end + + let!(:person_relationships) do + [ + { + kind: 'child', + relative: person_reference + } + ] + end + + let(:documents) { [] } + + let(:event_response) do + [ + { + received_at: DateTime.now, + body: "response" + } + ] + end + + let(:event_request) do + [ + { + requested_at: DateTime.now, + body: "request" + } + ] + end + + let!(:lawful_presence_determination) do + { + vlp_verified_at: DateTime.now, + vlp_authority: "curam", + vlp_document_id: nil, + citizen_status: nil, + citizenship_result: nil, + qualified_non_citizenship_result: nil, + aasm_state: "verification_successful", + ssa_responses: event_response, + ssa_requests: event_request, + vlp_responses: event_response, + vlp_requests: event_request + } + end + + let(:vlp_documents) do + [ + { + title: "untitled", + creator: "dchl", + subject: "Naturalization Certificate", + description: nil, + publisher: "dchl", + contributor: nil, + date: Date.today, + type: "text", + format: "application/octet-stream", + identifier: nil, + source: "enroll_system", + language: "en", + relation: nil, + coverage: nil, + rights: "public", + tags: [], size: nil, + doc_identifier: nil, + _type: "VlpDocument", + alien_number: "047122478", + i94_number: nil, + visa_number: nil, + passport_number: nil, + sevis_id: nil, + naturalization_number: "30121997", + receipt_number: nil, + citizenship_number: nil, + card_number: nil, + country_of_citizenship: nil, + expiration_date: nil, + issuing_country: nil, + status: "not submitted", + verification_type: "Citizenship", + comment: nil + } + ] + end + + let(:ridp_documents) do + [ + { + title: "license - back.pdf", + creator: "mhc", + subject: "license - back.pdf", + description: nil, + publisher: "mhc", + contributor: nil, + date: Date.today, + type: "text", + format: "application/octet-stream", + identifier: nil, source: "enroll_system", + language: "en", relation: nil, coverage: nil, + rights: 'public', + tags: [], + size: nil, + doc_identifier: nil, + status: "downloaded", + ridp_verification_type: "Identity", + comment: nil, + uploaded_at: Date.today + } + ] + end + + let(:verification_type_history_elements) do + [ + { verification_type: "Social Security Number", + action: "SSA Hub Request", + modifier: "Enroll App", + update_reason: "Hub request" } + ] + end + + let!(:consumer_role) do + { + five_year_bar: false, + requested_coverage_start_date: Date.today, + aasm_state: "fully_verified", + is_applicant: true, + birth_location: nil, + marital_status: nil, + is_active: true, + is_applying_coverage: true, + raw_event_responses: [], + bookmark_url: nil, + admin_bookmark_url: nil, + contact_method: "Only Paper communication", + language_preference: "spa", + is_state_resident: true, + identity_validation: "na", + application_validation: "na", + identity_update_reason: nil, + application_update_reason: nil, + identity_rejected: false, + application_rejected: false, + documents: [], + vlp_documents: vlp_documents, + ridp_documents: ridp_documents, + verification_type_history_elements: verification_type_history_elements, + lawful_presence_determination: lawful_presence_determination, + local_residency_responses: event_response, + local_residency_requests: event_request + } + end + + let!(:resident_role) do + { + is_applicant: true, + is_active: true, + bookmark_url: "/families/home", + is_state_resident: true, + residency_determined_at: Date.today, + contact_method: "Paper and Electronic communications", + language_preference: "English", + local_residency_responses: event_response, + lawful_presence_determination: lawful_presence_determination + } + end + + let!(:individual_market_transitions) do + [ + { + role_type: "consumer", + start_on: Date.today, + end_on: Date.today, + reason_code: "initial_individual_market_transition_created_using_data_migration", + submitted_at: DateTime.now + } + ] + end + + let!(:verification_types) do + [ + { + type_name: "DC Residency", + validation_status: "attested", + applied_roles: ["consumer_role"], + update_reason: nil, + rejected: false, + external_service: nil, + due_date: Date.today, + due_date_type: nil, + updated_by: person_reference, + inactive: nil, + vlp_documents: [] + } + ] + end + + let(:broker_agency_reference) do + { hbx_id: "1233444", market_kind: 'both', name: 'broker agency', dba: nil, display_name: nil, fein: '089441964', corporate_npn: nil } + end + + let!(:broker_role) do + { + aasm_state: "decertified", + npn: "2355863", + broker_agency_reference: broker_agency_reference, + provider_kind: "broker", + reason: "Broker has obtained carrier appointments and has completed training.", + market_kind: "both", + languages_spoken: ["en"], + working_hours: false, + accept_new_clients: nil, + license: nil, + training: nil, + carrier_appointments: + { + aetna_health_inc: nil, + aetna_life_insurance_company: nil, + carefirst_bluechoice_inc: nil, + group_hospitalization_and_medical_services_inc: nil, + kaiser_foundation: nil, optimum_choice: nil, + united_health_care_insurance: nil, + united_health_care_mid_atlantic: nil + } + } + end + + let(:addresses) do + [ + { + kind: "home", + address_1: "S Street NW", + address_2: "", + address_3: "", + city: "Washington", + county: "", + state: "ME", + location_state_code: nil, + full_text: nil, + zip: "20009", + country_name: "" + } + ] + end + + let(:emails) do + [ + { + kind: "home", + address: "test@gmail.com" + } + ] + end + + let(:timestamp) do + { + submitted_at: DateTime.now, + created_at: DateTime.now, + modified_at: DateTime.now + } + end + + let!(:input_params) do + { + hbx_id: '1001', + is_active: true, + is_disabled: false, + no_dc_address: nil, + no_dc_address_reason: nil, + is_homeless: nil, + is_temporarily_out_of_state: nil, + age_off_excluded: nil, + is_applying_for_assistance: nil, + person_name: person_name, + person_health: person_health, + person_demographics: person_demographics, + person_relationships: person_relationships, + consumer_role: consumer_role, + resident_role: resident_role, + individual_market_transitions: individual_market_transitions, + verification_types: verification_types, + broker_role: broker_role, + addresses: addresses, + phones: phones, + emails: emails, + documents: documents, + timestamp: timestamp + } + end + + let(:person_params) do + AcaEntities::Contracts::People::PersonContract.new.call(input_params) + end + + let(:person_entity) do + AcaEntities::People::Person.new(person_params.to_h) + end + + context 'with valid demographics' do + let!(:person_demographics) do + { + encrypted_ssn: "yobheUbYUK2Abfc6lrq37YQCsPgBL8lLkw==\n", + no_ssn: false, + gender: 'male', + dob: Date.today, + is_incarcerated: false, + language_code: 'es' + } + end + + let(:phones) do + [ + { + kind: "home", + country_code: "", + area_code: "202", + number: "2991290", + extension: "", + primary: true, + full_phone_number: "2022991290" + } + ] + end + + before do + @result = subject.call(person_entity) + @json_result = JSON.parse(@result.success) + end + + it 'should return success' do + expect(@result).to be_success + end + + it 'should have a preferred language code of spa' do + expect(@json_result["ridpRequest"]["primaryRequest"]["person"]["personPreferredLanguage"]).to eq 'spa' + end + + it 'should not include the blank suffix' do + expect(@json_result["ridpRequest"]["primaryRequest"]["person"].keys).not_to include "personSuffixName" + end + + end + + context 'with invalid demographics' do + let!(:person_name) do + { + first_name: ' ', + middle_name: 'middle name', + last_name: '' + } + end + + let!(:person_demographics) do + { + encrypted_ssn: "yobheUbYUK2Abfc6lrq37YQCsPgBL8lLkw==\n", + no_ssn: false, + gender: 'male', + dob: Date.today, + is_incarcerated: false, + language_code: 'es' + } + end + + let(:phones) do + [ + { + kind: "home", + country_code: "", + area_code: "202", + number: "2991290", + extension: "", + primary: true, + full_phone_number: "2022991290" + } + ] + end + + before do + @result = subject.call(person_entity) + end + + it 'should return failure' do + expect(@result).to be_failure + end + + end + end +end