+
diff --git a/app/views/state_file/state_file_pages/about_page.html.erb b/app/views/state_file/state_file_pages/about_page.html.erb
index 81b260d81e..923abdc04d 100644
--- a/app/views/state_file/state_file_pages/about_page.html.erb
+++ b/app/views/state_file/state_file_pages/about_page.html.erb
@@ -45,4 +45,13 @@
<% end %>
-<% end %>
\ No newline at end of file
+<% end %>
+
+<% if Flipper.enabled?(:get_your_pdf) %>
+
+<% end %>
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 7aaececb6f..3ed440700b 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -2063,6 +2063,17 @@ en:
04_detail_links_html: See our Terms and Conditions and Privacy Policy.
subheader: Please sign up here to receive a notification when we open in January.
state_file:
+ archived_intakes:
+ email_address:
+ edit:
+ enter_email: Enter the email address linked to your account
+ verification_code:
+ edit:
+ error_message: Incorrect verification code. After 2 failed attempts, accounts are locked.
+ subtitle_html: If you didn’t receive a code, please check your spam folder.
+ subtitle_html_2: If you still need help resubscribing, chat with us.
+ title_html: We’ve sent your code to %{email_address}
+ verify: Verify code
faq:
index:
title: Common questions from %{state} taxpayers
@@ -3900,6 +3911,7 @@ en:
Already filed your state taxes with us? You can download a copy of your state return until December 31, 2024
header: A free state filing service for taxpayers using IRS Direct File
helper_heading_html: "How does this service work? "
+ looking_for_return_html: "Looking for your 2023 Arizona or New York State Tax Return?"
section1_html: |
File and submit your federal return with IRS Direct File before using this service.
@@ -3919,6 +3931,7 @@ en:
In 2025, this service will support filing state tax returns in Arizona, Idaho, North Carolina, New Jersey, and Maryland.
subheader_2_html: To start filing your federal return, go to directfile.irs.gov.
subheader_3_html: "Already filed your federal return with IRS Direct File and need to complete your state return?Sign in here."
+ tax_return_link: Click here to access your tax return
card_postscript:
responses_saved_html: Your responses are saved. If you need a break, you can come back and log in to your account at fileyourstatetaxes.org.
coming_soon:
diff --git a/config/locales/es.yml b/config/locales/es.yml
index 2bf0feb258..d5cc23a966 100644
--- a/config/locales/es.yml
+++ b/config/locales/es.yml
@@ -2015,6 +2015,17 @@ es:
04_detail_links_html: Favor de consultar nuestras Condiciones generales y Política de privacidad.
subheader: Regístrese aquí para recibir una notificación cuando abramos en enero.
state_file:
+ archived_intakes:
+ email_address:
+ edit:
+ enter_email: Ingresa la dirección de correo electrónico vinculada a tu cuenta
+ verification_code:
+ edit:
+ error_message: Después de 2 intentos fallidos, las cuentas serán bloqueadas.
+ subtitle_html: Si no recibiste un código, revisa tu carpeta de spam. Si aún necesitas ayuda para volver a suscribirte, chatea con nosotros.
+ subtitle_html_2: Si aún necesitas ayuda para resuscribirte, chatea con nosotros.
+ title_html: Hemos enviado tu código a %{email_address}
+ verify: Verificar el código
faq:
index:
title: 'Preguntas frecuentes de los contribuyentes de %{state}:'
@@ -3874,6 +3885,7 @@ es:
¿Ya presentó tus impuestos estatales con nosotros? Puedes descargar una copia de tu declaración estatal hasta el 31 de diciembre de 2024
header: Una herramienta sin costo para presentar impuestos estatales para los contribuyentes utilizando IRS Direct File
helper_heading_html: "¿Cómo funciona este servicio?"
+ looking_for_return_html: "¿Buscas tu declaración de impuestos de Arizona o del estado de Nueva York de 2023?"
section1_html: |
Presenta y envía tu declaración federal con IRS Direct File antes de usar esta herramienta.
@@ -3893,6 +3905,7 @@ es:
En 2025, este servicio admitirá la presentación de declaraciones de impuestos estatales en Arizona, Idaho, Carolina del Norte, Nueva Jersey y Maryland.
subheader_2_html: Para comenzar a presentar tu declaración federal, visita directfile.irs.gov.
subheader_3_html: "¿Ya presentaste tu declaración federal con IRS Direct File y necesitas completar tu declaración estatal?Inicia sesión aquí."
+ tax_return_link: Haga clic aquí para acceder a su declaración de impuestos.
card_postscript:
responses_saved_html: Tus respuestas han sido guardadas. Si necesitas hacer una pausa, puedes regresar e iniciar sesión en tu cuenta en fileyourstatetaxes.org.
coming_soon:
@@ -4051,7 +4064,7 @@ es:
body_text: |
Hello!
- Your six-digit verification code for %{service_name} is: %{verification_code}. This code will expire after 30 minutes.
+ Tu codigo de verificacion de seis digitos para %{service_name}: %{verification_code}. This code will expire after 30 minutes.
Did you receive this code without signing up? Email us at help@%{service_name_lower}.org
diff --git a/config/routes.rb b/config/routes.rb
index 6d167bb0ce..78fa450c93 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -552,9 +552,17 @@ def scoped_navigation_routes(context, navigation)
get '/.well-known/pki-validation/:id', to: 'public_pages#pki_validation'
end
+ devise_for :state_file_archived_intake_requests
+
constraints(Routes::StateFileDomain.new) do
scope '(:locale)', locale: /#{I18n.available_locales.join('|')}/ do
namespace :state_file do
+ namespace :archived_intakes do
+ get 'email_address/edit', to: 'email_address#edit', as: 'edit_email_address'
+ patch 'email_address', to: 'email_address#update'
+ get 'verification_code/edit', to: 'verification_code#edit', as: 'edit_verification_code'
+ patch 'verification_code', to: 'verification_code#update'
+ end
namespace :questions do
get "show_xml", to: "confirmation#show_xml"
get "explain_calculations", to: "confirmation#explain_calculations"
diff --git a/db/migrate/20250108221853_state_file_archived_intake_requests.rb b/db/migrate/20250108221853_state_file_archived_intake_requests.rb
new file mode 100644
index 0000000000..51487edc81
--- /dev/null
+++ b/db/migrate/20250108221853_state_file_archived_intake_requests.rb
@@ -0,0 +1,13 @@
+class StateFileArchivedIntakeRequests < ActiveRecord::Migration[7.1]
+ def change
+ create_table :state_file_archived_intake_requests do |t|
+ t.belongs_to :state_file_archived_intakes, foreign_key: true
+ t.string 'ip_address'
+ t.string 'email_address'
+ t.timestamps
+ end
+
+ remove_foreign_key :state_file_archived_intake_access_logs, :state_file_archived_intakes
+ safety_assured { remove_column :state_file_archived_intake_access_logs, :state_file_archived_intakes_id }
+ end
+end
diff --git a/db/migrate/20250108223733_add_devise_to_state_file_archived_intake_requests.rb b/db/migrate/20250108223733_add_devise_to_state_file_archived_intake_requests.rb
new file mode 100644
index 0000000000..4f453ec1fb
--- /dev/null
+++ b/db/migrate/20250108223733_add_devise_to_state_file_archived_intake_requests.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+class AddDeviseToStateFileArchivedIntakeRequests < ActiveRecord::Migration[7.1]
+ def change
+ add_column :state_file_archived_intake_requests, :failed_attempts, :integer, default: 0, null: false
+ add_column :state_file_archived_intake_requests, :locked_at, :datetime
+ end
+end
diff --git a/db/migrate/20250108230315_add_foreign_keyto_state_file_archived_intake_access_logs.rb b/db/migrate/20250108230315_add_foreign_keyto_state_file_archived_intake_access_logs.rb
new file mode 100644
index 0000000000..062afa8263
--- /dev/null
+++ b/db/migrate/20250108230315_add_foreign_keyto_state_file_archived_intake_access_logs.rb
@@ -0,0 +1,6 @@
+class AddForeignKeytoStateFileArchivedIntakeAccessLogs < ActiveRecord::Migration[7.1]
+ def change
+ add_column :state_file_archived_intake_access_logs, :state_file_archived_intake_request_id, :bigint
+ add_foreign_key :state_file_archived_intake_access_logs, :state_file_archived_intake_requests, column: :state_file_archived_intake_request_id, validate: false
+ end
+end
diff --git a/db/migrate/20250108231212_validate_foreign_keyfor_state_file_archived_intake_access_logs.rb b/db/migrate/20250108231212_validate_foreign_keyfor_state_file_archived_intake_access_logs.rb
new file mode 100644
index 0000000000..0f849150dc
--- /dev/null
+++ b/db/migrate/20250108231212_validate_foreign_keyfor_state_file_archived_intake_access_logs.rb
@@ -0,0 +1,5 @@
+class ValidateForeignKeyforStateFileArchivedIntakeAccessLogs < ActiveRecord::Migration[7.1]
+ def change
+ validate_foreign_key :state_file_archived_intake_access_logs, :state_file_archived_intake_requests
+ end
+end
diff --git a/db/migrate/20250113222716_remove_state_file_archived_intake_requests_fields.rb b/db/migrate/20250113222716_remove_state_file_archived_intake_requests_fields.rb
new file mode 100644
index 0000000000..b9df5450a7
--- /dev/null
+++ b/db/migrate/20250113222716_remove_state_file_archived_intake_requests_fields.rb
@@ -0,0 +1,7 @@
+class RemoveStateFileArchivedIntakeRequestsFields < ActiveRecord::Migration[7.1]
+ def change
+ safety_assured do
+ remove_column :state_file_archived_intake_access_logs, :ip_address
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 8f0ae6dbfa..f7d1c6ccdb 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[7.1].define(version: 2025_01_08_233940) do
+ActiveRecord::Schema[7.1].define(version: 2025_01_13_222716) do
# These are extensions that must be enabled in order to support this database
enable_extension "citext"
enable_extension "plpgsql"
@@ -1688,10 +1688,19 @@
t.datetime "created_at", null: false
t.jsonb "details", default: "{}"
t.integer "event_type"
+ t.bigint "state_file_archived_intake_request_id"
+ t.datetime "updated_at", null: false
+ end
+
+ create_table "state_file_archived_intake_requests", force: :cascade do |t|
+ t.datetime "created_at", null: false
+ t.string "email_address"
+ t.integer "failed_attempts", default: 0, null: false
t.string "ip_address"
+ t.datetime "locked_at"
t.bigint "state_file_archived_intakes_id"
t.datetime "updated_at", null: false
- t.index ["state_file_archived_intakes_id"], name: "idx_on_state_file_archived_intakes_id_e878049c06"
+ t.index ["state_file_archived_intakes_id"], name: "idx_on_state_file_archived_intakes_id_31501c23f8"
end
create_table "state_file_archived_intakes", force: :cascade do |t|
@@ -2829,7 +2838,8 @@
add_foreign_key "site_coordinator_roles_vita_partners", "site_coordinator_roles"
add_foreign_key "site_coordinator_roles_vita_partners", "vita_partners"
add_foreign_key "source_parameters", "vita_partners"
- add_foreign_key "state_file_archived_intake_access_logs", "state_file_archived_intakes", column: "state_file_archived_intakes_id"
+ add_foreign_key "state_file_archived_intake_access_logs", "state_file_archived_intake_requests"
+ add_foreign_key "state_file_archived_intake_requests", "state_file_archived_intakes", column: "state_file_archived_intakes_id"
add_foreign_key "state_routing_fractions", "state_routing_targets"
add_foreign_key "state_routing_fractions", "vita_partners"
add_foreign_key "system_notes", "clients"
diff --git a/spec/controllers/state_file/archived_intake/archived_intake_controller_spec.rb b/spec/controllers/state_file/archived_intake/archived_intake_controller_spec.rb
new file mode 100644
index 0000000000..440e2075f2
--- /dev/null
+++ b/spec/controllers/state_file/archived_intake/archived_intake_controller_spec.rb
@@ -0,0 +1,76 @@
+require 'rails_helper'
+
+describe StateFile::ArchivedIntakes::ArchivedIntakeController, type: :controller do
+ let(:ip_address) { '192.168.0.1' }
+ let(:email_address) { 'test@example.com' }
+ let!(:request_instance) { create :state_file_archived_intake_request, ip_address: ip_address, email_address: email_address }
+ before do
+ allow(controller).to receive(:ip_for_irs).and_return(ip_address)
+ session[:email_address] = email_address
+ end
+
+ describe '#current_request' do
+ it 'finds the StateFileArchivedIntakeRequest by IP and email address' do
+ expect(controller.current_request).to eq(request_instance)
+ end
+
+ it 'returns nil if no request is found' do
+ session[:email_address] = "non_existant_email@bad.com"
+
+ expect(controller.current_request).to be_nil
+ end
+ end
+
+ describe '#create_state_file_access_log' do
+ let(:event_type) { 'incorrect_ssn_challenge' }
+
+ before do
+ allow(controller).to receive(:current_request).and_return(request_instance)
+ end
+
+ it 'creates a StateFileArchivedIntakeAccessLog with the correct attributes' do
+ result = controller.create_state_file_access_log(event_type)
+
+ expect(result).to be_a(StateFileArchivedIntakeAccessLog)
+ expect(result.event_type).to eq event_type
+ expect(result.state_file_archived_intake_request).to eq request_instance
+ end
+
+ context 'when current return is nil' do
+ before do
+ allow(controller).to receive(:current_request).and_return(nil)
+ end
+ it 'create a StateFileArchivedIntakeAccessLog' do
+ result = controller.create_state_file_access_log(event_type)
+
+ expect(result).to be_a(StateFileArchivedIntakeAccessLog)
+ expect(result.event_type).to eq event_type
+ expect(result.state_file_archived_intake_request).to eq nil
+ end
+ end
+
+ describe '#check_feature_flag' do
+ context 'when the feature flag is enabled' do
+ before do
+ allow(Flipper).to receive(:enabled?).with(:get_your_pdf).and_return(true)
+ end
+
+ it 'does not redirect' do
+ expect(controller).not_to receive(:redirect_to)
+ controller.check_feature_flag
+ end
+ end
+
+ context 'when the feature flag is disabled' do
+ before do
+ allow(Flipper).to receive(:enabled?).with(:get_your_pdf).and_return(false)
+ end
+
+ it 'redirects to the root path' do
+ expect(controller).to receive(:redirect_to).with(root_path)
+ controller.check_feature_flag
+ end
+ end
+ end
+ end
+end
diff --git a/spec/controllers/state_file/archived_intake/email_address_controller_spec.rb b/spec/controllers/state_file/archived_intake/email_address_controller_spec.rb
new file mode 100644
index 0000000000..84a0cd9d4d
--- /dev/null
+++ b/spec/controllers/state_file/archived_intake/email_address_controller_spec.rb
@@ -0,0 +1,85 @@
+require "rails_helper"
+
+RSpec.describe StateFile::ArchivedIntakes::EmailAddressController, type: :controller do
+ before do
+ Flipper.enable(:get_your_pdf)
+ end
+ describe "GET #edit" do
+ it "renders the edit template with a new EmailAddressForm" do
+ get :edit
+
+ expect(assigns(:form)).to be_a(StateFile::ArchivedIntakes::EmailAddressForm)
+ expect(response).to render_template(:edit)
+ end
+ end
+
+ describe "POST #update" do
+ let(:valid_email_address) { "test@example.com" }
+ let(:invalid_email_address) { "" }
+ let(:ip_address) { "127.0.0.1" }
+
+ before do
+ allow(controller).to receive(:ip_for_irs).and_return(ip_address)
+ end
+
+ context "when the form is valid" do
+ context "and a archived intake exists with the email address" do
+ let!(:archived_intake) { create :state_file_archived_intake, email_address: valid_email_address }
+ it "creates an access log create a request and redirects to the verification code page" do
+ post :update, params: {
+ state_file_archived_intakes_email_address_form: { email_address: valid_email_address }
+ }
+ expect(assigns(:form)).to be_valid
+
+ request = StateFileArchivedIntakeRequest.last
+ expect(request.ip_address).to eq(ip_address)
+ expect(request.email_address).to eq(valid_email_address)
+ expect(request.state_file_archived_intakes_id).to eq(archived_intake.id)
+
+ log = StateFileArchivedIntakeAccessLog.last
+ expect(log.state_file_archived_intake_request_id).to eq(request.id)
+ expect(log.event_type).to eq("issued_email_challenge")
+
+ expect(response).to redirect_to(
+ state_file_archived_intakes_edit_verification_code_path
+ )
+ end
+ end
+
+ context "and a archived does not exist with the email address" do
+ it "creates an access log create a request and redirects to the verification code page" do
+ post :update, params: {
+ state_file_archived_intakes_email_address_form: { email_address: valid_email_address }
+ }
+ expect(assigns(:form)).to be_valid
+
+ request = StateFileArchivedIntakeRequest.last
+ expect(request.ip_address).to eq(ip_address)
+ expect(request.email_address).to eq(valid_email_address)
+ expect(request.state_file_archived_intakes_id).to eq(nil)
+
+ log = StateFileArchivedIntakeAccessLog.last
+ expect(log.state_file_archived_intake_request_id).to eq(request.id)
+ expect(log.event_type).to eq("issued_email_challenge")
+
+ expect(response).to redirect_to(
+ state_file_archived_intakes_edit_verification_code_path
+ )
+ end
+ end
+ end
+ context "when the form is invalid" do
+ it "renders the edit template" do
+ post :update, params: {
+ state_file_archived_intakes_email_address_form: { email_address: invalid_email_address }
+ }
+
+ expect(assigns(:form)).not_to be_valid
+
+ expect(StateFileArchivedIntakeAccessLog.count).to eq(0)
+
+ expect(response).to render_template(:edit)
+ end
+ end
+ end
+end
diff --git a/spec/controllers/state_file/archived_intake/verification_code_controller_spec.rb b/spec/controllers/state_file/archived_intake/verification_code_controller_spec.rb
new file mode 100644
index 0000000000..f9c2c91929
--- /dev/null
+++ b/spec/controllers/state_file/archived_intake/verification_code_controller_spec.rb
@@ -0,0 +1,100 @@
+require "rails_helper"
+
+RSpec.describe StateFile::ArchivedIntakes::VerificationCodeController, type: :controller do
+ let(:current_request) { create(:state_file_archived_intake_request, email_address:email_address, failed_attempts: 0) }
+ let(:email_address) { "test@example.com" }
+ let(:valid_verification_code) { "123456" }
+ let(:invalid_verification_code) { "654321" }
+
+ before do
+ Flipper.enable(:get_your_pdf)
+ allow(controller).to receive(:current_request).and_return(current_request)
+ allow(I18n).to receive(:locale).and_return(:en)
+ end
+
+ describe "GET #edit" do
+ context "when the request is locked" do
+ before do
+ allow(current_request).to receive(:access_locked?).and_return(true)
+ end
+
+ it "redirects to the root path" do
+ get :edit
+
+ expect(response).to redirect_to(root_path)
+ end
+ end
+
+ context "when the request is not locked" do
+ before do
+ allow(current_request).to receive(:access_locked?).and_return(false)
+ end
+
+ it "renders the edit template with a new VerificationCodeForm and queues a job" do
+ expect{
+ get :edit
+ }.to have_enqueued_job(ArchivedIntakeEmailVerificationCodeJob).with(
+ email_address: email_address,
+ locale: :en
+ )
+
+ expect(assigns(:form)).to be_a(StateFile::ArchivedIntakes::VerificationCodeForm)
+ expect(assigns(:email_address)).to eq(email_address)
+ expect(response).to render_template(:edit)
+ end
+ end
+ end
+
+ describe "POST #update" do
+ context "with a valid verification code" do
+ before do
+ allow_any_instance_of(StateFile::ArchivedIntakes::VerificationCodeForm).to receive(:valid?).and_return(true)
+ end
+
+ it "creates a success access log and does not increment failed_attempts" do
+ expect {
+ post :update, params: { state_file_archived_intakes_verification_code_form: { verification_code: valid_verification_code } }
+ }.to change(StateFileArchivedIntakeAccessLog, :count).by(1)
+
+ log = StateFileArchivedIntakeAccessLog.last
+ expect(log.event_type).to eq("correct_email_code")
+ expect(current_request.failed_attempts).to eq(0)
+ expect(response).to redirect_to(root_path)
+ end
+ end
+
+ context "with an invalid verification code" do
+ before do
+ allow_any_instance_of(StateFile::ArchivedIntakes::VerificationCodeForm).to receive(:valid?).and_return(false)
+ end
+
+ it "creates a failure access log, increments failed_attempts, and re-renders edit on first failed attempt" do
+ expect {
+ post :update, params: { state_file_archived_intakes_verification_code_form: { verification_code: invalid_verification_code } }
+ }.to change(StateFileArchivedIntakeAccessLog, :count).by(1)
+
+ log = StateFileArchivedIntakeAccessLog.last
+ expect(log.event_type).to eq("incorrect_email_code")
+
+ expect(current_request.reload.failed_attempts).to eq(1)
+ expect(assigns(:form)).to be_a(StateFile::ArchivedIntakes::VerificationCodeForm)
+ expect(response).to render_template(:edit)
+ end
+
+ it "locks the account and redirects to root path after multiple failed attempts" do
+ current_request.update!(failed_attempts: 1)
+
+ expect {
+ post :update, params: { state_file_archived_intakes_verification_code_form: { verification_code: invalid_verification_code } }
+ }.to change(StateFileArchivedIntakeAccessLog, :count).by(2)
+
+ log = StateFileArchivedIntakeAccessLog.last
+ expect(log.event_type).to eq("client_lockout_begin")
+
+ expect(current_request.reload.failed_attempts).to eq(2)
+ expect(current_request.reload.access_locked?).to be_truthy
+ expect(response).to redirect_to(root_path)
+ end
+ end
+ end
+end
diff --git a/spec/factories/state_file_archived_intake_access_logs.rb b/spec/factories/state_file_archived_intake_access_logs.rb
index 38637da7b9..6c094cd6dd 100644
--- a/spec/factories/state_file_archived_intake_access_logs.rb
+++ b/spec/factories/state_file_archived_intake_access_logs.rb
@@ -2,21 +2,16 @@
#
# Table name: state_file_archived_intake_access_logs
#
-# id :bigint not null, primary key
-# details :jsonb
-# event_type :integer
-# ip_address :string
-# created_at :datetime not null
-# updated_at :datetime not null
-# state_file_archived_intakes_id :bigint
-#
-# Indexes
-#
-# idx_on_state_file_archived_intakes_id_e878049c06 (state_file_archived_intakes_id)
+# id :bigint not null, primary key
+# details :jsonb
+# event_type :integer
+# created_at :datetime not null
+# updated_at :datetime not null
+# state_file_archived_intake_request_id :bigint
#
# Foreign Keys
#
-# fk_rails_... (state_file_archived_intakes_id => state_file_archived_intakes.id)
+# fk_rails_... (state_file_archived_intake_request_id => state_file_archived_intake_requests.id)
#
FactoryBot.define do
factory :state_file_archived_intake_access_log do
diff --git a/spec/factories/state_file_archived_intake_requests.rb b/spec/factories/state_file_archived_intake_requests.rb
new file mode 100644
index 0000000000..b74af2e44a
--- /dev/null
+++ b/spec/factories/state_file_archived_intake_requests.rb
@@ -0,0 +1,32 @@
+# == Schema Information
+#
+# Table name: state_file_archived_intake_requests
+#
+# id :bigint not null, primary key
+# email_address :string
+# failed_attempts :integer default(0), not null
+# ip_address :string
+# locked_at :datetime
+# created_at :datetime not null
+# updated_at :datetime not null
+# state_file_archived_intakes_id :bigint
+#
+# Indexes
+#
+# idx_on_state_file_archived_intakes_id_31501c23f8 (state_file_archived_intakes_id)
+#
+# Foreign Keys
+#
+# fk_rails_... (state_file_archived_intakes_id => state_file_archived_intakes.id)
+#
+FactoryBot.define do
+ factory :state_file_archived_intake_request do
+ email_address { "geddy_lee@gmail.com" }
+ failed_attempts { 0 }
+ locked_at { nil }
+
+ trait :locked do
+ locked_at { 5.minutes.ago }
+ end
+ end
+end
diff --git a/spec/factories/state_file_archived_intakes.rb b/spec/factories/state_file_archived_intakes.rb
index 19391c29f0..d7e3c35201 100644
--- a/spec/factories/state_file_archived_intakes.rb
+++ b/spec/factories/state_file_archived_intakes.rb
@@ -17,6 +17,15 @@
#
FactoryBot.define do
factory :state_file_archived_intake do
+ email_address { "geddy_lee@example.com" }
+ hashed_ssn { "hashed_ssn_value" }
+ mailing_apartment { "Apt 1" }
+ mailing_city { "Test City" }
+ mailing_state { "CA" }
+ mailing_street { "123 Test Street" }
+ mailing_zip { "12345" }
+ state_code { "CA" }
+ tax_year { 2023 }
submission_pdf { nil }
transient do
@@ -26,20 +35,22 @@
after(:create) do |archived_intake, evaluator|
intake = evaluator.intake
- archiver = evaluator.archiver
- archived_intake.update(
- email_address: intake&.email_address,
- hashed_ssn: intake&.hashed_ssn,
- mailing_apartment: intake&.direct_file_data&.mailing_apartment,
- mailing_city: intake&.direct_file_data&.mailing_city,
- mailing_state: intake&.direct_file_data&.mailing_state,
- mailing_street: intake&.direct_file_data&.mailing_street,
- mailing_zip: intake&.direct_file_data&.mailing_zip,
- tax_year: archiver&.tax_year,
- state_code: archiver&.state_code,
- )
- archived_intake.submission_pdf.attach(intake&.submission_pdf&.blob)
- archived_intake.save!
+ unless intake.nil?
+ archiver = evaluator.archiver
+ archived_intake.update(
+ email_address: intake&.email_address,
+ hashed_ssn: intake&.hashed_ssn,
+ mailing_apartment: intake&.direct_file_data&.mailing_apartment,
+ mailing_city: intake&.direct_file_data&.mailing_city,
+ mailing_state: intake&.direct_file_data&.mailing_state,
+ mailing_street: intake&.direct_file_data&.mailing_street,
+ mailing_zip: intake&.direct_file_data&.mailing_zip,
+ tax_year: archiver&.tax_year,
+ state_code: archiver&.state_code,
+ )
+ archived_intake.submission_pdf.attach(intake&.submission_pdf&.blob)
+ archived_intake.save!
+ end
end
end
end
diff --git a/spec/features/state_file/prior_year_access_spec.rb b/spec/features/state_file/prior_year_access_spec.rb
new file mode 100644
index 0000000000..ef53ef901a
--- /dev/null
+++ b/spec/features/state_file/prior_year_access_spec.rb
@@ -0,0 +1,35 @@
+require "rails_helper"
+
+RSpec.feature "accessing a prior year PDF", active_job: true do
+
+ before do
+ allow_any_instance_of(Routes::StateFileDomain).to receive(:matches?).and_return(true)
+ end
+
+ context "get_your_pdf flag is enabled" do
+ before do
+ Flipper.enable(:get_your_pdf)
+ end
+
+ it "has content" do
+ visit "/"
+ click_on I18n.t("state_file.state_file_pages.about_page.tax_return_link")
+ fill_in I18n.t("state_file.questions.email_address.edit.email_address_label"), with: "someone@example.com"
+ click_on I18n.t("state_file.questions.email_address.edit.action")
+ expect(page).to have_text "We’ve sent your code to someone@example.com"
+ perform_enqueued_jobs
+ mail = ActionMailer::Base.deliveries.last
+ code = mail.html_part.body.to_s.match(%r{ (\d{6})\.})[1]
+ fill_in "Enter the 6-digit code", with: code
+ click_on I18n.t("state_file.archived_intakes.verification_code.edit.verify")
+ expect(current_path).to eq(root_path)
+ end
+ end
+
+ context "get_your_pdf flag is not enabled" do
+ it "has content" do
+ visit "/"
+ expect(page).to_not have_text I18n.t("state_file.state_file_pages.about_page.tax_return_link")
+ end
+ end
+end
diff --git a/spec/forms/state_file/archived_intakes/email_address_form_spec.rb b/spec/forms/state_file/archived_intakes/email_address_form_spec.rb
new file mode 100644
index 0000000000..25dcd098cd
--- /dev/null
+++ b/spec/forms/state_file/archived_intakes/email_address_form_spec.rb
@@ -0,0 +1,37 @@
+require "rails_helper"
+
+RSpec.describe StateFile::ArchivedIntakes::EmailAddressForm do
+ describe "#valid?" do
+ context "when the email address is valid" do
+ it "returns true" do
+ form = StateFile::ArchivedIntakes::EmailAddressForm.new(email_address: "test@example.com")
+
+ expect(form.valid?).to be true
+ end
+ end
+
+ context "when the email address is invalid" do
+ it "returns false for an improperly formatted email" do
+ form = StateFile::ArchivedIntakes::EmailAddressForm.new(email_address: "invalid-email")
+
+ expect(form.valid?).to be false
+ expect(form.errors[:email_address]).to include("Please enter a valid email address.")
+ end
+
+ it "returns false when the email is blank" do
+ form = StateFile::ArchivedIntakes::EmailAddressForm.new(email_address: "")
+
+ expect(form.valid?).to be false
+ expect(form.errors[:email_address]).to include("Can't be blank.")
+ end
+ end
+ end
+
+ describe "#initialize" do
+ it "assigns attributes correctly" do
+ form = StateFile::ArchivedIntakes::EmailAddressForm.new(email_address: "test@example.com")
+
+ expect(form.email_address).to eq("test@example.com")
+ end
+ end
+end
diff --git a/spec/forms/state_file/archived_intakes/verification_code_form_spec.rb b/spec/forms/state_file/archived_intakes/verification_code_form_spec.rb
new file mode 100644
index 0000000000..123921ad6f
--- /dev/null
+++ b/spec/forms/state_file/archived_intakes/verification_code_form_spec.rb
@@ -0,0 +1,64 @@
+require "rails_helper"
+
+RSpec.describe StateFile::ArchivedIntakes::VerificationCodeForm do
+ describe "#valid?" do
+ context "when the verification code is present and valid" do
+ it "returns true" do
+ allow(VerificationCodeService).to receive(:hash_verification_code_with_contact_info)
+ .with("test@example.com", "123456")
+ .and_return("hashed_code")
+
+ allow(EmailAccessToken).to receive_message_chain(:lookup, :exists?).and_return(true)
+
+ form = StateFile::ArchivedIntakes::VerificationCodeForm.new(
+ { verification_code: "123456" },
+ email_address: "test@example.com"
+ )
+
+ expect(form.valid?).to be true
+ end
+ end
+
+ context "when the verification code is present but invalid" do
+ it "adds an error and returns false" do
+ allow(VerificationCodeService).to receive(:hash_verification_code_with_contact_info)
+ .with("test@example.com", "123456")
+ .and_return("hashed_code")
+
+ allow(EmailAccessToken).to receive_message_chain(:lookup, :exists?).and_return(false)
+
+ form = StateFile::ArchivedIntakes::VerificationCodeForm.new(
+ { verification_code: "123456" },
+ email_address: "test@example.com"
+ )
+
+ expect(form.valid?).to be false
+ expect(form.errors[:verification_code]).to include("Incorrect verification code. After 2 failed attempts, accounts are locked.")
+ end
+ end
+
+ context "when the verification code is blank" do
+ it "adds an error and returns false" do
+ form = StateFile::ArchivedIntakes::VerificationCodeForm.new(
+ { verification_code: "" },
+ email_address: "test@example.com"
+ )
+
+ expect(form.valid?).to be false
+ expect(form.errors[:verification_code]).to include("Incorrect verification code. After 2 failed attempts, accounts are locked.")
+ end
+ end
+ end
+
+ describe "#initialize" do
+ it "assigns attributes correctly" do
+ form = StateFile::ArchivedIntakes::VerificationCodeForm.new(
+ { verification_code: "123456" },
+ email_address: "test@example.com"
+ )
+
+ expect(form.verification_code).to eq("123456")
+ expect(form.email_address).to eq("test@example.com")
+ end
+ end
+end
diff --git a/spec/jobs/archived_intake_email_verification_code_job_spec.rb b/spec/jobs/archived_intake_email_verification_code_job_spec.rb
new file mode 100644
index 0000000000..465c871d87
--- /dev/null
+++ b/spec/jobs/archived_intake_email_verification_code_job_spec.rb
@@ -0,0 +1,20 @@
+require "rails_helper"
+
+RSpec.describe ArchivedIntakeEmailVerificationCodeJob, type: :job do
+ before do
+ allow(ArchivedIntakeEmailVerificationCodeService).to receive(:request_code)
+ end
+
+ describe "#perform" do
+ context "with email_address, visitor_id, and locale params" do
+ it "requests a verification code by email using those params" do
+ ArchivedIntakeEmailVerificationCodeJob.perform_now(email_address: "client@example.com", locale: "es")
+
+ expect(ArchivedIntakeEmailVerificationCodeService).to have_received(:request_code).with(
+ email_address: "client@example.com",
+ locale: "es"
+ )
+ end
+ end
+ end
+end
diff --git a/spec/models/state_file_archived_intake_access_log_spec.rb b/spec/models/state_file_archived_intake_access_log_spec.rb
index 0c8b0e1989..b65c56e7bd 100644
--- a/spec/models/state_file_archived_intake_access_log_spec.rb
+++ b/spec/models/state_file_archived_intake_access_log_spec.rb
@@ -2,21 +2,16 @@
#
# Table name: state_file_archived_intake_access_logs
#
-# id :bigint not null, primary key
-# details :jsonb
-# event_type :integer
-# ip_address :string
-# created_at :datetime not null
-# updated_at :datetime not null
-# state_file_archived_intakes_id :bigint
-#
-# Indexes
-#
-# idx_on_state_file_archived_intakes_id_e878049c06 (state_file_archived_intakes_id)
+# id :bigint not null, primary key
+# details :jsonb
+# event_type :integer
+# created_at :datetime not null
+# updated_at :datetime not null
+# state_file_archived_intake_request_id :bigint
#
# Foreign Keys
#
-# fk_rails_... (state_file_archived_intakes_id => state_file_archived_intakes.id)
+# fk_rails_... (state_file_archived_intake_request_id => state_file_archived_intake_requests.id)
#
require 'rails_helper'
diff --git a/spec/models/state_file_archived_intake_request_spec.rb b/spec/models/state_file_archived_intake_request_spec.rb
new file mode 100644
index 0000000000..4aea07c2d6
--- /dev/null
+++ b/spec/models/state_file_archived_intake_request_spec.rb
@@ -0,0 +1,35 @@
+# == Schema Information
+#
+# Table name: state_file_archived_intake_requests
+#
+# id :bigint not null, primary key
+# email_address :string
+# failed_attempts :integer default(0), not null
+# ip_address :string
+# locked_at :datetime
+# created_at :datetime not null
+# updated_at :datetime not null
+# state_file_archived_intakes_id :bigint
+#
+# Indexes
+#
+# idx_on_state_file_archived_intakes_id_31501c23f8 (state_file_archived_intakes_id)
+#
+# Foreign Keys
+#
+# fk_rails_... (state_file_archived_intakes_id => state_file_archived_intakes.id)
+#
+require "rails_helper"
+
+describe StateFileArchivedIntakeRequest do
+ describe "#increment_failed_attempts" do
+ let!(:request_instance) { create :state_file_archived_intake_request, failed_attempts: 1 }
+ it "locks access when failed attempts is incremented to 2" do
+ expect(request_instance.access_locked?).to eq(false)
+
+ request_instance.increment_failed_attempts
+
+ expect(request_instance.access_locked?).to eq(true)
+ end
+ end
+end
diff --git a/spec/services/archived_intake_email_verification_code_service_spec.rb b/spec/services/archived_intake_email_verification_code_service_spec.rb
new file mode 100644
index 0000000000..9786b489f9
--- /dev/null
+++ b/spec/services/archived_intake_email_verification_code_service_spec.rb
@@ -0,0 +1,49 @@
+require "rails_helper"
+
+describe ArchivedIntakeEmailVerificationCodeService do
+ let(:email_address) { "example@example.com" }
+ let(:locale) { "en" }
+
+ let(:params) do
+ {
+ email_address: email_address,
+ locale: locale
+ }
+ end
+
+ describe ".request_code" do
+ let(:mailer_double) { double VerificationCodeMailer }
+ let(:access_token_double) { double EmailAccessToken }
+ let(:mocked_job_response) { double }
+ before do
+ allow(EmailAccessToken).to receive(:generate!).and_return(["123456", access_token_double])
+ allow(VerificationEmail).to receive(:create!)
+ allow(mailer_double).to receive(:perform_now).and_return(mocked_job_response)
+ allow_any_instance_of(Mail::Message).to receive(:message_id).and_return("mocked_mailer_id")
+ allow(DatadogApi).to receive(:increment)
+ end
+
+
+ context "when locale is English" do
+ let(:service_type) { :statefile }
+
+ it "sends an email that includes 'FileYourStateTaxes'" do
+ described_class.request_code(**params)
+ email = ActionMailer::Base.deliveries.last
+ expect(email.to).to eq [email_address]
+ expect(email.body.encoded).to include "Your six-digit verification code for FileYourStateTaxes is: 123456"
+ end
+ end
+
+ context 'when locale is Spanish' do
+ let(:locale) { :es }
+
+ it "sends an email that includes 'FileYourStateTaxes'" do
+ described_class.request_code(**params)
+ email = ActionMailer::Base.deliveries.last
+ expect(email.to).to eq [email_address]
+ expect(email.body.encoded).to include ("Tu codigo de verificacion de seis digitos para FileYourStateTaxes:")
+ end
+ end
+ end
+end