diff --git a/modules/vye/app/models/vye/user_info.rb b/modules/vye/app/models/vye/user_info.rb index a50ce81e882..4cae7864942 100644 --- a/modules/vye/app/models/vye/user_info.rb +++ b/modules/vye/app/models/vye/user_info.rb @@ -27,6 +27,7 @@ class Vye::UserInfo < ApplicationRecord enum indicator: { chapter1606: 'A', chapter1607: 'E', chapter30: 'B', D: 'D' } delegate :icn, to: :user_profile, allow_nil: true + delegate :ssn, to: :mpi_profile, allow_nil: true delegate :pending_documents, to: :user_profile delegate :verifications, to: :user_profile @@ -45,10 +46,6 @@ def verification_required verifications.empty? end - def ssn - mpi_profile&.ssn - end - private def mpi_profile diff --git a/modules/vye/lib/tasks/vye.rake b/modules/vye/lib/tasks/vye.rake index e82afb3abf4..a9674415cdc 100644 --- a/modules/vye/lib/tasks/vye.rake +++ b/modules/vye/lib/tasks/vye.rake @@ -1,6 +1,15 @@ # frozen_string_literal: true namespace :vye do + namespace :feature do + desc 'Enables request_allowed feature flag' + task request_allowed: :environment do |_cmd, _args| + current_state = Flipper.enabled?(:vye_request_allowed) + puts format('Current state vye_request_allowed is: %s', current_state:) + Flipper.enable :vye_request_allowed + end + end + namespace :install do desc 'Installs config into config/settings.local.yml' task config: :environment do |_cmd, _args| @@ -13,4 +22,53 @@ namespace :vye do local_path.write(engine_dev_path.read, mode: 'a') end end + + namespace :data do + desc 'Clear VYE data from the database' + task clear: :environment do |_cmd, _args| + Vye::AddressChange.destroy_all + Vye::DirectDepositChange.destroy_all + Vye::Verification.destroy_all + Vye::Award.destroy_all + Vye::UserInfo.destroy_all + + Vye::PendingDocument.destroy_all + + Vye::UserProfile.destroy_all + end + + desc 'Build YAML files to load for development from team sensitive data' + task build: :environment do |_cmd, _args| + source = Pathname('/projects/va.gov-team-sensitive') + target = Rails.root / 'tmp' + handles = nil + + build = Vye::StagingData::Build.new(target:) do |paths| + handles = + paths + .transform_values do |value| + (source / value).open + end + end + + build.dump + handles.each_value(&:close) + end + + desc 'Load development YAML files into the database' + task :load, [:path] => :environment do |_cmd, args| + raise 'load path is required' if args[:path].nil? + + root = Pathname(args[:path]) + files = root.glob('**/*.yaml') + raise "No files found in #{root}" if files.empty? + + files.each do |file| + source = :team_sensitive + data = YAML.safe_load(file.read, permitted_classes: [Date, DateTime, Symbol, Time]) + records = data.slice(:profile, :info, :address, :awards, :pending_documents) + Vye::LoadData.new(source:, records:) + end + end + end end diff --git a/modules/vye/lib/vye/staging_data/build.rb b/modules/vye/lib/vye/staging_data/build.rb new file mode 100644 index 00000000000..36ae85826f4 --- /dev/null +++ b/modules/vye/lib/vye/staging_data/build.rb @@ -0,0 +1,154 @@ +# frozen_string_literal: true + +module Vye + module StagingData + class Build + MAX_AWARD_COUNT = 4 + MAX_PENDING_DOCUMENT_COUNT = 1 + PATHS = + { + test_users: 'Administrative/vagov-users/test_users.csv', + mvi_staging_users: 'Administrative/vagov-users/mvi-staging-users.csv' + }.freeze + + private_constant :PATHS, :MAX_AWARD_COUNT, :MAX_PENDING_DOCUMENT_COUNT + + def dump + @dump if defined?(@dump) + + rows.each do |row| + summary = row[:summary] + path = root / format('%s.yaml', summary[:full_name].downcase.gsub(/\s+/, '-')) + path.write(row.to_yaml) + end + + @dump = true + end + + private + + attr_reader :test_users, :mvi_staging_users, :target + + def initialize(target:) + yield(PATHS) => {test_users:, mvi_staging_users:} + @test_users = CSV.new(test_users, headers: true).each.to_a + @mvi_staging_users = CSV.new(mvi_staging_users, headers: true).each.to_a + @target = target + end + + def cross + return @cross if defined?(@cross) + + product = + test_users + .product(mvi_staging_users) + .select do |tu, msu| + tu['ssn'] == msu['ssn'] + end + + @cross = product.group_by { |x| x.first['ssn'] }.values.pluck(0) + end + + def rows + @rows ||= + cross + .map do |tu, msu| + tu = extract_from_tu(tu) + msu = extract_from_msu(msu) + + summary = {}.merge(tu).merge(msu) + + { + summary:, + profile: fake_user_profile(summary:), + info: fake_user_info(summary:), + address: fake_address_change(summary:), + awards: fake_awards, + pending_documents: fake_pending_documents + } + end + end + + def root + return @root if defined?(@root) + + timestamp = Time.zone.now.strftime('%Y%m%dT%H%M%S%z') + root = target / format('vye/staging-data-%s', timestamp:) + root.mkpath + + @root = root + end + + def fake_user_profile(summary:) + FactoryBot.attributes_for(:vye_user_profile).except(:ssn, :icn, :file_number).tap do |record| + record[:ssn] = summary[:ssn] + record[:file_number] = summary[:ssn] + record[:icn] = summary[:icn] + end + end + + def fake_user_info(summary:) + FactoryBot.attributes_for(:vye_user_info).except(:full_name).tap do |record| + name = summary[:full_name] + parts = name.split(/\s+/) + initials = parts.pluck(0).join + rest = parts[-1][1..(7 - initials.length)] + stub_nm = [initials, rest].join.upcase + + record[:stub_nm] = stub_nm + record[:file_number] = summary[:ssn] + end + end + + def fake_address_change(summary:) + FactoryBot.attributes_for(:vye_address_backend).tap do |record| + record[:veteran_name] = summary[:full_name] + end + end + + def fake_awards + (1..rand(1..4)).map do + FactoryBot.attributes_for(:vye_award) + end + end + + def fake_pending_documents + (0..rand(0..1)).map do + FactoryBot.attributes_for(:vye_pending_document) + end + end + + def extract_from_tu(row) + ssn = scrub_ssn(row['ssn']) + idme_uuid = row['idme_uuid']&.strip + email = row['email']&.strip + password = row['password']&.strip + full_name = + row.values_at( + 'first_name', + 'middle_name', + 'last_name' + ).compact.map(&:strip).map(&:capitalize).join(' ').strip + + { ssn:, idme_uuid:, email:, password:, full_name: } + end + + def extract_from_msu(row) + ssn = scrub_ssn(row['ssn']) + icn = row['icn']&.strip + full_name = + row.values_at( + 'first_name', + 'middle_name', + 'last_name' + ).compact.map(&:strip).map(&:capitalize).join(' ').strip + + { ssn:, icn:, full_name: } + end + + def scrub_ssn(value) + value&.gsub(/\D/, '')&.strip + end + end + end +end diff --git a/modules/vye/spec/factories/vye/address_changes.rb b/modules/vye/spec/factories/vye/address_changes.rb index 40bc66818a8..6c3a882994e 100644 --- a/modules/vye/spec/factories/vye/address_changes.rb +++ b/modules/vye/spec/factories/vye/address_changes.rb @@ -9,4 +9,13 @@ zip_code { Faker::Address.zip_code } origin { Vye::AddressChange.origins['frontend'] } end + + factory :vye_address_backend, class: 'Vye::AddressChange' do + veteran_name { Faker::Name.name } + address1 { Faker::Address.street_address } + city { Faker::Address.city } + state { Faker::Address.state_abbr } + zip_code { Faker::Address.zip_code } + origin { Vye::AddressChange.origins['backend'] } + end end diff --git a/modules/vye/spec/lib/vye/staging_data/build_spec.rb b/modules/vye/spec/lib/vye/staging_data/build_spec.rb new file mode 100644 index 00000000000..f6555bfdf0e --- /dev/null +++ b/modules/vye/spec/lib/vye/staging_data/build_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Vye::StagingData::Build do + describe '#dump' do + let(:target) { double('Pathname (Target)') } + + let(:source) { double('Pathname (Source)') } + + let(:streams) do + { + test_users: StringIO.new(<<~TEST_USERS), + first_name,middle_name,last_name,gender,birth_date,ssn,phone,email,password,mfa_code,id_types,loa,idme_uuid,logingov_uuid,services,notes + John,A,Doe,M,1932-02-05T00:00:00-08:00,111111111,800-827-1000,user1@email.com,xxx,xxx,"idme,logingov",3,xxx,xxx,"notes", + Jane,B,Smith,M,1933-04-05T00:00:00-08:00,222222222,800-827-1000,user2@email.com,xxx,xxx,"idme,logingov",3,xxx,xxx,"notes", + TEST_USERS + mvi_staging_users: StringIO.new(<<~MVI_STAGING_USERS) + first_name,middle_name,last_name,gender,birth_date,ssn,phone,email,password,icn,edipi,has_data_for, notes + John,A,Doe,M,1932-02-05T00:00:00-08:00,111111111,800-827-1000,user1@email.com,xxx,xxx,xxx,,notes + Jane,B,Smith,M,1933-04-05T00:00:00-08:00,222222222,800-827-1000,user2@email.com,xxx,xxx,xxx,, + MVI_STAGING_USERS + }.freeze + end + + let(:staging_data_build) do + Vye::StagingData::Build.new(target:) do |_paths| + streams + end + end + + it 'returns an array of rows' do + root = double('Pathname (Root)') + dump_file = double('Pathname (File)') + + expect(target).to receive(:/).and_return(root) + expect(root).to receive(:mkpath).with(no_args).and_return(true) + expect(root).to receive(:/).twice.with(any_args).and_return(dump_file) + expect(dump_file).to receive(:write).twice.and_return(true) + + staging_data_build.dump + end + end +end diff --git a/modules/vye/spec/serializers/verification_serializer_spec.rb b/modules/vye/spec/serializers/verification_serializer_spec.rb new file mode 100644 index 00000000000..f614e1e1cb5 --- /dev/null +++ b/modules/vye/spec/serializers/verification_serializer_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Vye::VerificationSerializer, type: :serializer do + let(:resource) { build(:vye_verification) } # Assuming you have a factory for verification + let(:serializer) { described_class.new(resource) } + let(:serialization) { ActiveModelSerializers::Adapter.create(serializer, {}) } + + it 'includes the expected attributes' do + expect do + serialization.as_json + end.not_to raise_error + end +end