From df914a8aad85e6bf952e9c943e02d9ff8d521394 Mon Sep 17 00:00:00 2001 From: Adrian Rollett Date: Tue, 26 Mar 2024 10:25:04 -0600 Subject: [PATCH 1/9] Codespaces native (#15935) * Add hybrid setup config * Various fixes * Improve startup * Don't try to bring app up in start * Add mockdata config * Improve startup * More QoL improvements * Improve startup * Add ruby lsp ext after start * Create log dir on start * Improvements * Change setup to facilitate ruby lsp extension installation * Remove manual extension installation * Fix startup script * More work on deps * Set user id when starting deps * More adjustments to startup scripts * simplify docker deps setup * Wait for the api to be ready before exiting the startup script * More QoL improvements * Change setup order * Add output for permissions set * Adjust permissions approach * One more adjustment * Belt & suspenders * More perms adjustment * Simplify startup * Add native setup * Change ruby setup to enable * Add hardware requirements * Add test DB config * Add docs * Update postgres version * Update redis version * Fix foreman output * Use different redis package * Minor debug output * Don't bother with starting the app * Improve test database config * Update doc * Remove host requirements * Only notify for port forwarding on 3000 * Allow access from localhost and shared github URLs. * Change ownership * Remove host header check This was obviated by the addition of the HostAuthorization middleware in Rails 6 * Add test DB setup to main local settings file * Update tests --- .devcontainer/devcontainer.json | 64 +++++++++++++++++++ .devcontainer/post-create.sh | 51 +++++++++++++++ .devcontainer/post-start.sh | 9 +++ .devcontainer/welcome.txt | 9 +++ .github/CODEOWNERS | 2 + README.md | 1 + app/controllers/concerns/headers.rb | 8 +-- docs/setup/codespaces.md | 27 ++++++++ docs/setup/native.md | 14 ++-- .../exceptions/not_a_safe_host_error.rb | 21 ------ .../application_controller_spec.rb | 9 --- .../exceptions/not_a_safe_host_error_spec.rb | 28 -------- 12 files changed, 171 insertions(+), 72 deletions(-) create mode 100644 .devcontainer/devcontainer.json create mode 100644 .devcontainer/post-create.sh create mode 100644 .devcontainer/post-start.sh create mode 100644 .devcontainer/welcome.txt create mode 100644 docs/setup/codespaces.md delete mode 100644 lib/common/exceptions/not_a_safe_host_error.rb delete mode 100644 spec/lib/common/exceptions/not_a_safe_host_error_spec.rb diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000000..69f4e6059e0 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,64 @@ +{ + "name": "vets-api native setup", + "image": "mcr.microsoft.com/devcontainers/base:bullseye", + + "features": { + "ghcr.io/devcontainers-contrib/features/ruby-asdf:0": { + "version": "3.2.3" + }, + "ghcr.io/robbert229/devcontainer-features/postgresql-client:1": { + "version": "15" + }, + "ghcr.io/devcontainers-contrib/features/redis-homebrew:1": { + "version": "6.2" + } + }, + + "forwardPorts": [ + 3000, + 9293, + 5432, + 6379 + ], + "portsAttributes": { + "3000": { + "label": "vets-api", + "onAutoForward": "notify", + "requireLocalPort": true + }, + "9293": { + "label": "vets-api-healthcheck", + "onAutoForward": "silent", + "requireLocalPort": true + }, + "5432": { + "label": "postgis", + "onAutoForward": "silent", + "requireLocalPort": true + }, + "6379": { + "label": "redis", + "onAutoForward": "silent", + "requireLocalPort": true + } + }, + + "postCreateCommand": "sh .devcontainer/post-create.sh", + "postStartCommand": "sh .devcontainer/post-start.sh", + + "customizations": { + "codespaces": { + "repositories": { + "department-of-veterans-affairs/vets-api-mockdata": { + "permissions": { + "contents": "read", + "pull_requests": "write" + } + } + } + }, + "vscode": { + "extensions": ["ms-azuretools.vscode-docker", "Shopify.ruby-lsp"] + } + } +} diff --git a/.devcontainer/post-create.sh b/.devcontainer/post-create.sh new file mode 100644 index 00000000000..b01f08ae3ab --- /dev/null +++ b/.devcontainer/post-create.sh @@ -0,0 +1,51 @@ +#!/bin/sh + +# Add welcome message +sudo cp .devcontainer/welcome.txt /usr/local/etc/vscode-dev-containers/first-run-notice.txt + +# Switch to vets-api ruby version +export PATH="${HOME}/.asdf/shims:${HOME}/.asdf/bin:${PATH}" +asdf install ruby $( cat .ruby-version ) +asdf global ruby $( cat .ruby-version ) + +git clone https://github.com/department-of-veterans-affairs/vets-api-mockdata.git ../vets-api-mockdata + +sudo apt update +sudo apt install -y libpq-dev pdftk shared-mime-info postgresql-15-postgis-3 + +gem install bundler +NUM_CORES=$( cat /proc/cpuinfo | grep '^processor'|wc -l ) +bundle config --global jobs `expr $NUM_CORES - 1` + +# Update test DB config +echo 'test_database_url: postgis://postgres:password@localhost:5432/vets_api_test?pool=4' > config/settings/test.local.yml + +# Add service config +if [ ! -f config/settings.local.yml ]; then + cp config/settings.local.yml.example config/settings.local.yml + cat <> config/settings.local.yml +database_url: postgis://postgres:password@localhost:5432/vets_api_development?pool=4 +test_database_url: postgis://postgres:password@localhost:5432/vets_api_test?pool=4 + +redis: + host: localhost + port: 6379 + app_data: + url: redis://localhost:6379 + sidekiq: + url: redis://localhost:6379 + +betamocks: + cache_dir: ../vets-api-mockdata + +# Allow access from localhost and shared github URLs. +virtual_hosts: ["127.0.0.1", "localhost", !ruby/regexp /.*\.app\.github\.dev/] +EOT +fi + +mkdir -p log +nohup bash -c '/home/linuxbrew/.linuxbrew/opt/redis@6.2/bin/redis-server /home/linuxbrew/.linuxbrew/etc/redis.conf' >> log/redis.log 2>&1 & +sudo /etc/init.d/postgresql restart +pg_isready -t 60 +sudo -u root sudo -u postgres psql -c "ALTER USER postgres PASSWORD 'password';" +./bin/setup diff --git a/.devcontainer/post-start.sh b/.devcontainer/post-start.sh new file mode 100644 index 00000000000..0abf46ab86d --- /dev/null +++ b/.devcontainer/post-start.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +echo "Starting redis..." +nohup /home/linuxbrew/.linuxbrew/opt/redis@6.2/bin/redis-server /home/linuxbrew/.linuxbrew/etc/redis.conf >> log/redis.log 2>&1 & + +echo "Starting postgres..." +sudo /etc/init.d/postgresql restart +echo "Waiting for postgres to be ready..." +pg_isready -t 60 diff --git a/.devcontainer/welcome.txt b/.devcontainer/welcome.txt new file mode 100644 index 00000000000..0e846774f27 --- /dev/null +++ b/.devcontainer/welcome.txt @@ -0,0 +1,9 @@ +~~~~~~ Welcome to vets-api on codespaces! ~~~~~~ + +For more information, see the codespaces README in docs/setup. + +~~~~~~ Quickstart ~~~~~ + +To start vets-api, run this command: + +foreman start -m all=1,clamd=0,freshclam=0 diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ab300977b7c..271f77d0628 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -11,6 +11,7 @@ Gemfile @department-of-veterans-affairs/backend-review-group Gemfile.lock @department-of-veterans-affairs/backend-review-group Jenkinsfile @department-of-veterans-affairs/backend-review-group Makefile @department-of-veterans-affairs/backend-review-group +.devcontainer @department-of-veterans-affairs/backend-review-group @department-of-veterans-affairs/cto-engineers app/controllers/appeals_base_controller.rb @department-of-veterans-affairs/backend-review-group app/controllers/appeals_base_controller_v1.rb @department-of-veterans-affairs/backend-review-group app/controllers/application_controller.rb @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group @@ -777,6 +778,7 @@ docs/setup/native.md @department-of-veterans-affairs/backend-review-group @depar docs/setup/rswag_setup.md @department-of-veterans-affairs/backend-review-group @department-of-veterans-affairs/va-api-engineers docs/setup/running_docker.md @department-of-veterans-affairs/backend-review-group @department-of-veterans-affairs/va-api-engineers docs/setup/running_natively.md @department-of-veterans-affairs/backend-review-group @department-of-veterans-affairs/va-api-engineers +docs/setup/codespaces.md @department-of-veterans-affairs/backend-review-group @department-of-veterans-affairs/cto-engineers docs/setup/va_forms.md @department-of-veterans-affairs/platform-va-product-forms @department-of-veterans-affairs/backend-review-group @department-of-veterans-affairs/va-api-engineers docs/setup/virtual_machine_access.md @department-of-veterans-affairs/backend-review-group @department-of-veterans-affairs/va-api-engineers .github @department-of-veterans-affairs/backend-review-group diff --git a/README.md b/README.md index 6fda13c1f7a..ec3b03f5948 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ For frontend, see [vets-website](https://github.com/department-of-veterans-affai - [Native setup](docs/setup/native.md) (OSX/Ubuntu) - [Docker setup](docs/setup/docker.md) - [Hybrid setup](docs/setup/hybrid.md) + - [Codespaces setup](docs/setup/codespaces.md) ## Running the app diff --git a/app/controllers/concerns/headers.rb b/app/controllers/concerns/headers.rb index 650763d8183..33987f5f7a3 100644 --- a/app/controllers/concerns/headers.rb +++ b/app/controllers/concerns/headers.rb @@ -3,13 +3,7 @@ module Headers extend ActiveSupport::Concern - included { prepend_before_action :block_unknown_hosts, :set_app_info_headers } - - # returns a Bad Request if the incoming host header is unsafe. - def block_unknown_hosts - return if controller_name == 'example' - raise Common::Exceptions::NotASafeHostError, request.host unless Settings.virtual_hosts.include?(request.host) - end + included { prepend_before_action :set_app_info_headers } def set_app_info_headers headers['X-Git-SHA'] = AppInfo::GIT_REVISION diff --git a/docs/setup/codespaces.md b/docs/setup/codespaces.md new file mode 100644 index 00000000000..55dda1bc97f --- /dev/null +++ b/docs/setup/codespaces.md @@ -0,0 +1,27 @@ +# Codespaces setup + +## About codespaces + +Github Codespaces provide an Integrated Development Environment (IDE) that is accessible entirely in a web browser. It is essentially a web based version of VS Code running on a cloud based virtual machine. + +Codespaces is available for all members of the Department of Veterans Affairs organization on Github. + +### More information + +- [Platform documentation for codespaces](https://depo-platform-documentation.scrollhelp.site/developer-docs/using-github-codespaces) +- See the #codespaces channel in Slack for additional questions about using Codespaces. + +## Creating a codespace + +1. Go to [your Codespaces page](https://github.com/codespaces) on Github. +2. Click the [new Codespace](https://github.com/codespaces/new) button at the top right. +3. Select the vets-api repository and adjust other settings as desired, the defaults should work well. +4. Click the 'Create codespace' button + +Your new codespace will open in Visual Studio Code if you have it installed locally, or otherwise in the browser. The vets-api repo and all dependencies will be installed, and it will be ready for use in about 5 minutes. + +## Using your codespace + +Your codespace will automatically start vets-api and forward port 3000 to your local machine if you have Visual Studio Code installed. The API can be accessed at http://localhost:3000/ + +For more information on running vets-api and specs, see the [native running instructions](running_natively.md). diff --git a/docs/setup/native.md b/docs/setup/native.md index 62928893891..8ec15de8acc 100644 --- a/docs/setup/native.md +++ b/docs/setup/native.md @@ -3,7 +3,7 @@ Vets API requires: - Ruby 3.2.3 -- PostgreSQL 11.x (including PostGIS 2.5) +- PostgreSQL 15.x (including PostGIS 3) - Redis 5.0.x The most up-to-date versions of each key dependency will be specified in the `docker-compose.yml` [file](https://github.com/department-of-veterans-affairs/vets-api/blob/master/docker-compose.yml) and the `Dockerfile`. @@ -114,14 +114,14 @@ All of the OSX instructions assume `homebrew` is your [package manager](https:// 1. It is MUCH easier to use the [Postgres.app](https://postgresapp.com/downloads.html) which installs the correct combination of Postgresql and PostGIS versions. - - Download the Postgres.app with PostgreSQL 10, 11 and 12 + - Download the Postgres.app with PostgreSQL 15 - Install Instructions here: https://postgresapp.com/ - `sudo mkdir -p /etc/paths.d && echo /Applications/Postgres.app/Contents/Versions/latest/bin | sudo tee /etc/paths.d/postgresapp` - `ARCHFLAGS="-arch x86_64" gem install pg -v 1.2.3` - 2. Alternatively Postgresql 11 & PostGIS 2.5 can be installed with homebrew - - `brew install postgresql@11` - - `brew services start postgresql@11` - - Install the `pex` manager to add your Postgresql 11 extensions from [here](https://github.com/petere/pex#installation) + 2. Alternatively Postgresql 15 & PostGIS 3 can be installed with homebrew + - `brew install postgresql@15` + - `brew services start postgresql@15` + - Install the `pex` manager to add your Postgresql 15 extensions from [here](https://github.com/petere/pex#installation) - Install the `postgis` extension along with a number of patches using the instructions summarized [here](https://gist.github.com/skissane/0487c097872a7f6d0dcc9bcd120c2ccd): - ```bash PG_CPPFLAGS='-DACCEPT_USE_OF_DEPRECATED_PROJ_API_H -I/usr/local/include' CFLAGS='-DACCEPT_USE_OF_DEPRECATED_PROJ_API_H -I/usr/local/include' pex install postgis @@ -181,7 +181,7 @@ All of the OSX instructions assume `homebrew` is your [package manager](https:// 2. Install PostGIS ```bash - sudo apt install -y postgresql-11-postgis-2.5 + sudo apt install -y postgresql-15-postgis-3 sudo -i -u postgres createuser postgis_test diff --git a/lib/common/exceptions/not_a_safe_host_error.rb b/lib/common/exceptions/not_a_safe_host_error.rb deleted file mode 100644 index f1aaaeae1cd..00000000000 --- a/lib/common/exceptions/not_a_safe_host_error.rb +++ /dev/null @@ -1,21 +0,0 @@ -# frozen_string_literal: true - -require 'common/exceptions/base_error' -require 'common/exceptions/serializable_error' - -module Common - module Exceptions - # Parameter Missing - required parameter was not provided - class NotASafeHostError < BaseError - attr_reader :host - - def initialize(host) - @host = host - end - - def errors - Array(SerializableError.new(i18n_interpolated(detail: { host: @host }))) - end - end - end -end diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index 60eb403fdce..0ff3e3437b1 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -478,15 +478,6 @@ def append_info_to_payload(payload) expect(controller.payload[:user_uuid]).to eq(user.uuid) end - context 'with a virtual host that is invalid' do - let(:header_host_value) { 'unsafe_host' } - - it 'returns bad request' do - get :test_authentication - expect(response).to have_http_status(:bad_request) - end - end - context 'with a credential that is locked' do let(:user) { build(:user, :loa3, :idme_lock) } diff --git a/spec/lib/common/exceptions/not_a_safe_host_error_spec.rb b/spec/lib/common/exceptions/not_a_safe_host_error_spec.rb deleted file mode 100644 index 56ecabfdd32..00000000000 --- a/spec/lib/common/exceptions/not_a_safe_host_error_spec.rb +++ /dev/null @@ -1,28 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -describe Common::Exceptions::NotASafeHostError do - context 'with no attributes provided' do - it do - expect { described_class.new } - .to raise_error(ArgumentError, 'wrong number of arguments (given 0, expected 1)') - end - end - - context 'with host provided' do - subject { described_class.new('unsafe_host') } - - it 'implements #errors which returns an array' do - expect(subject.errors).to be_an(Array) - end - - it 'the errors object has all relevant keys' do - expect(subject.errors.first.to_hash) - .to eq(title: 'Bad Request', - detail: '"unsafe_host" is not a safe host', - code: '110', - status: '400') - end - end -end From 0ec7386718d1d933d6dbefcf6174249d7bb9508c Mon Sep 17 00:00:00 2001 From: Rockwell Windsor Rice <129893414+rockwellwindsor-va@users.noreply.github.com> Date: Tue, 26 Mar 2024 11:33:44 -0500 Subject: [PATCH 2/9] API-32311 establish poa oas updates (#16005) * API-32311 establish poa oas updates * Updates OAS text * Updates Swagger Docs modified: modules/claims_api/app/swagger/claims_api/description/v2.md modified: modules/claims_api/app/swagger/claims_api/v2/dev/swagger.json modified: modules/claims_api/app/swagger/claims_api/v2/production/swagger.json * Updates for removed text * Fixes missed line removal * Adds missed docs text update * Adds 2122, 2122a, 212/vallidate, 2122a/validate and power-of-attorney/{id} endpoints to docs --- .../app/swagger/claims_api/description/v2.md | 11 +- .../swagger/claims_api/v2/dev/swagger.json | 20 +- .../claims_api/v2/production/swagger.json | 2917 ++++++++++++++++- .../veterans/rswag_power_of_attorney_spec.rb | 10 +- .../claims_api/spec/support/rswag_config.rb | 2 +- 5 files changed, 2930 insertions(+), 30 deletions(-) diff --git a/modules/claims_api/app/swagger/claims_api/description/v2.md b/modules/claims_api/app/swagger/claims_api/description/v2.md index cb01ef73d9e..50f6fe94d65 100644 --- a/modules/claims_api/app/swagger/claims_api/description/v2.md +++ b/modules/claims_api/app/swagger/claims_api/description/v2.md @@ -6,14 +6,11 @@ The Benefits Claims API Version 2 lets internal consumers: - Automatically establish an Intent To File (21-0966) in VBMS - Automatically establish a disability compensation claim (21-526EZ) in VBMS - Digitally submit supporting documentation for disability compensation claims -- Retrieve the active Power of Attorney for a Veteran +- Retrieve the active Power of Attorney organization of individual with power of attorney for a Veteran +- Automatically establish a power of attorney appointment in VBMS for an accredited organization (VA Form 21-22). +- Automatically establish a power of attorney appointment in VBMS for an accredited individual (VA Form 21-22a). -Additional functionality will be added over time. - -You should use the [Benefits Claims API Version 1](https://developer.va.gov/explore/benefits/docs/claims?version=current) if:  - -- You are a consumer outside of VA and do not have the necessary VA agreements to use this API, and/or -- You want automatic establishment of power of attorney (21-22 or 21-22a) +You should use the [Benefits Claims API Version 1](https://developer.va.gov/explore/benefits/docs/claims?version=current) if you are a consumer outside of VA and do not have the necessary VA agreements to use this API. ## Technical Overview diff --git a/modules/claims_api/app/swagger/claims_api/v2/dev/swagger.json b/modules/claims_api/app/swagger/claims_api/v2/dev/swagger.json index 45d2acf2493..0298380c65d 100644 --- a/modules/claims_api/app/swagger/claims_api/v2/dev/swagger.json +++ b/modules/claims_api/app/swagger/claims_api/v2/dev/swagger.json @@ -3,7 +3,7 @@ "info": { "title": "Benefits Claims", "version": "v2", - "description": "## Background\n\nThe Benefits Claims API Version 2 lets internal consumers: \n\n- Retrieve existing claim information, including status, by claim ID\n- Automatically establish an Intent To File (21-0966) in VBMS\n- Automatically establish a disability compensation claim (21-526EZ) in VBMS\n- Digitally submit supporting documentation for disability compensation claims\n- Retrieve the active Power of Attorney for a Veteran\n\nAdditional functionality will be added over time.\n\nYou should use the [Benefits Claims API Version 1](https://developer.va.gov/explore/benefits/docs/claims?version=current) if: \n\n- You are a consumer outside of VA and do not have the necessary VA agreements to use this API, and/or\n- You want automatic establishment of power of attorney (21-22 or 21-22a)\n \n## Technical Overview\n\nThis API accepts a payload of requests and responses with the payload identifying the claim and Veteran. Responses provide the submission’s processing status. Responses also provide a unique ID which can be used with the appropriate GET endpoint to return detailed, end-to-end claims status tracking. \n\nEnd-to-end claims tracking provides the status of claims as they move through the submission process, but does not return whether the claim was approved or denied. \n\n### Claim statuses\n\nClaims are first submitted by this API and then established in Veterans Benefits Management System (VBMS). A 200 response means that the claim was successfully submitted by the API. It does not mean VA has received the claim. Use the appropriate GET endpoint and the ID returned with your submission response to confirm the status of the submission. Statuses are:\n\n- Pending - the claim is successfully submitted for processing\n- Errored - the submission encountered upstream errors\n- Canceled - the claim was identified as a duplicate or another issue caused the claim to be canceled. For duplicate claims, the tracking of the claim's progress happens under a different Claim ID . \n\nOther statuses this API returns align with the [VA.gov](http://va.gov/) [claim status descriptions](https://www.va.gov/resources/what-your-claim-status-means/), which are:\n\n- Claim received\n- Initial review\n- Evidence gathering, review, and decision\n- Preparation for notification\n- Complete\n\n### Finding a Veteran's unique VA ID\n\nThis API uses a unique Veteran identifier to identify the subject of each API request. This Veteran identifier can be retrieved by passing the Veteran’s first name, last name, DOB, and SSN to the ‘/veteran-id’ endpoint. This identifier should then be used as the Veteran ID parameter in request URLs.\n\nNote: though Veteran identifiers are typically static, they may change over time. If a specific Veteran ID suddenly responds with a ‘404 not found’ error, the identifier may have changed. It’s a good idea to periodically check the identifier for each Veteran.\n\n### Authentication and authorization\n\nThe authentication model for the Benefits Claims Version 2 is based on OAuth 2.0 / OpenID Connect and supports the [client credentials grant](https://developer.va.gov/explore/authorization/docs/client-credentials?api=claims).\n\n**Important**: To get production access, you must either work for VA or have specific VA agreements in place. If you have questions, [contact us](https://developer.va.gov/support/contact-us).\n\n### Test data for sandbox environment use\n\nWe use mock [test data in the sandbox environment](https://github.com/department-of-veterans-affairs/vets-api-clients/blob/master/test_accounts.md). Sandbox test data and test users for the Benefits Claims API are valid for all versions of the API.\n" + "description": "## Background\n\nThe Benefits Claims API Version 2 lets internal consumers: \n\n- Retrieve existing claim information, including status, by claim ID\n- Automatically establish an Intent To File (21-0966) in VBMS\n- Automatically establish a disability compensation claim (21-526EZ) in VBMS\n- Digitally submit supporting documentation for disability compensation claims\n- Retrieve the active Power of Attorney organization of individual with power of attorney for a Veteran\n- Automatically establish a power of attorney appointment in VBMS for an accredited organization (VA Form 21-22).\n- Automatically establish a power of attorney appointment in VBMS for an accredited individual (VA Form 21-22a).\n\nYou should use the [Benefits Claims API Version 1](https://developer.va.gov/explore/benefits/docs/claims?version=current) if you are a consumer outside of VA and do not have the necessary VA agreements to use this API.\n \n## Technical Overview\n\nThis API accepts a payload of requests and responses with the payload identifying the claim and Veteran. Responses provide the submission’s processing status. Responses also provide a unique ID which can be used with the appropriate GET endpoint to return detailed, end-to-end claims status tracking. \n\nEnd-to-end claims tracking provides the status of claims as they move through the submission process, but does not return whether the claim was approved or denied. \n\n### Claim statuses\n\nClaims are first submitted by this API and then established in Veterans Benefits Management System (VBMS). A 200 response means that the claim was successfully submitted by the API. It does not mean VA has received the claim. Use the appropriate GET endpoint and the ID returned with your submission response to confirm the status of the submission. Statuses are:\n\n- Pending - the claim is successfully submitted for processing\n- Errored - the submission encountered upstream errors\n- Canceled - the claim was identified as a duplicate or another issue caused the claim to be canceled. For duplicate claims, the tracking of the claim's progress happens under a different Claim ID . \n\nOther statuses this API returns align with the [VA.gov](http://va.gov/) [claim status descriptions](https://www.va.gov/resources/what-your-claim-status-means/), which are:\n\n- Claim received\n- Initial review\n- Evidence gathering, review, and decision\n- Preparation for notification\n- Complete\n\n### Finding a Veteran's unique VA ID\n\nThis API uses a unique Veteran identifier to identify the subject of each API request. This Veteran identifier can be retrieved by passing the Veteran’s first name, last name, DOB, and SSN to the ‘/veteran-id’ endpoint. This identifier should then be used as the Veteran ID parameter in request URLs.\n\nNote: though Veteran identifiers are typically static, they may change over time. If a specific Veteran ID suddenly responds with a ‘404 not found’ error, the identifier may have changed. It’s a good idea to periodically check the identifier for each Veteran.\n\n### Authentication and authorization\n\nThe authentication model for the Benefits Claims Version 2 is based on OAuth 2.0 / OpenID Connect and supports the [client credentials grant](https://developer.va.gov/explore/authorization/docs/client-credentials?api=claims).\n\n**Important**: To get production access, you must either work for VA or have specific VA agreements in place. If you have questions, [contact us](https://developer.va.gov/support/contact-us).\n\n### Test data for sandbox environment use\n\nWe use mock [test data in the sandbox environment](https://github.com/department-of-veterans-affairs/vets-api-clients/blob/master/test_accounts.md). Sandbox test data and test users for the Benefits Claims API are valid for all versions of the API.\n" }, "tags": [ { @@ -28,7 +28,7 @@ }, { "name": "Power of Attorney", - "description": "Allows authenticated and authorized users to retrieve the active power of attorney for a Veteran\n" + "description": "Allows authenticated and authorized users to automatically establish power of attorney appointments to an organization or an individual. Organizations and individuals must be VA accredited representatives.\n" } ], "components": { @@ -5720,7 +5720,7 @@ "application/json": { "example": { "data": { - "id": "bb700978-5b4c-435d-b8ec-d16914ed3174", + "id": "cf532e4e-e89e-4f9c-aebd-ce8361336a41", "type": "forms/526", "attributes": { "veteran": { @@ -7804,8 +7804,8 @@ "id": "1", "type": "intent_to_file", "attributes": { - "creationDate": "2024-03-14", - "expirationDate": "2025-03-14", + "creationDate": "2024-03-26", + "expirationDate": "2025-03-26", "type": "compensation", "status": "active" } @@ -8524,7 +8524,7 @@ "status": "422", "detail": "Could not retrieve Power of Attorney due to multiple representatives with code: A1Q", "source": { - "pointer": "/modules/claims_api/app/controllers/claims_api/v2/veterans/power_of_attorney/base_controller.rb:111:in `representative'" + "pointer": "/modules/claims_api/app/controllers/claims_api/v2/veterans/power_of_attorney/base_controller.rb:112:in `representative'" } } ] @@ -8623,7 +8623,7 @@ "application/json": { "example": { "data": { - "id": "3d2234a1-0151-4ddc-921f-7e3575089d6e", + "id": "29b16b36-3108-411f-9f5f-2c1c2e147ea3", "type": "individual", "attributes": { "code": "083", @@ -9323,7 +9323,7 @@ "application/json": { "example": { "data": { - "id": "45bf00c5-36b3-4157-9620-6a89d5bd37ae", + "id": "a7114d11-8ffd-4545-ad99-d70e74991e11", "type": "organization", "attributes": { "code": "083", @@ -11261,11 +11261,11 @@ "application/json": { "example": { "data": { - "id": "d3048cf3-3869-4c31-a873-996e4959b13a", + "id": "7b0c58e1-4bf7-413c-a3ab-c8f6b95208b0", "type": "claimsApiPowerOfAttorneys", "attributes": { "status": "submitted", - "dateRequestAccepted": "2024-03-14", + "dateRequestAccepted": "2024-03-26", "representative": { "serviceOrganization": { "poaCode": "074" diff --git a/modules/claims_api/app/swagger/claims_api/v2/production/swagger.json b/modules/claims_api/app/swagger/claims_api/v2/production/swagger.json index aeeecb629ae..43450b6625d 100644 --- a/modules/claims_api/app/swagger/claims_api/v2/production/swagger.json +++ b/modules/claims_api/app/swagger/claims_api/v2/production/swagger.json @@ -3,7 +3,7 @@ "info": { "title": "Benefits Claims", "version": "v2", - "description": "## Background\n\nThe Benefits Claims API Version 2 lets internal consumers: \n\n- Retrieve existing claim information, including status, by claim ID\n- Automatically establish an Intent To File (21-0966) in VBMS\n- Automatically establish a disability compensation claim (21-526EZ) in VBMS\n- Digitally submit supporting documentation for disability compensation claims\n- Retrieve the active Power of Attorney for a Veteran\n\nAdditional functionality will be added over time.\n\nYou should use the [Benefits Claims API Version 1](https://developer.va.gov/explore/benefits/docs/claims?version=current) if: \n\n- You are a consumer outside of VA and do not have the necessary VA agreements to use this API, and/or\n- You want automatic establishment of power of attorney (21-22 or 21-22a)\n \n## Technical Overview\n\nThis API accepts a payload of requests and responses with the payload identifying the claim and Veteran. Responses provide the submission’s processing status. Responses also provide a unique ID which can be used with the appropriate GET endpoint to return detailed, end-to-end claims status tracking. \n\nEnd-to-end claims tracking provides the status of claims as they move through the submission process, but does not return whether the claim was approved or denied. \n\n### Claim statuses\n\nClaims are first submitted by this API and then established in Veterans Benefits Management System (VBMS). A 200 response means that the claim was successfully submitted by the API. It does not mean VA has received the claim. Use the appropriate GET endpoint and the ID returned with your submission response to confirm the status of the submission. Statuses are:\n\n- Pending - the claim is successfully submitted for processing\n- Errored - the submission encountered upstream errors\n- Canceled - the claim was identified as a duplicate or another issue caused the claim to be canceled. For duplicate claims, the tracking of the claim's progress happens under a different Claim ID . \n\nOther statuses this API returns align with the [VA.gov](http://va.gov/) [claim status descriptions](https://www.va.gov/resources/what-your-claim-status-means/), which are:\n\n- Claim received\n- Initial review\n- Evidence gathering, review, and decision\n- Preparation for notification\n- Complete\n\n### Finding a Veteran's unique VA ID\n\nThis API uses a unique Veteran identifier to identify the subject of each API request. This Veteran identifier can be retrieved by passing the Veteran’s first name, last name, DOB, and SSN to the ‘/veteran-id’ endpoint. This identifier should then be used as the Veteran ID parameter in request URLs.\n\nNote: though Veteran identifiers are typically static, they may change over time. If a specific Veteran ID suddenly responds with a ‘404 not found’ error, the identifier may have changed. It’s a good idea to periodically check the identifier for each Veteran.\n\n### Authentication and authorization\n\nThe authentication model for the Benefits Claims Version 2 is based on OAuth 2.0 / OpenID Connect and supports the [client credentials grant](https://developer.va.gov/explore/authorization/docs/client-credentials?api=claims).\n\n**Important**: To get production access, you must either work for VA or have specific VA agreements in place. If you have questions, [contact us](https://developer.va.gov/support/contact-us).\n\n### Test data for sandbox environment use\n\nWe use mock [test data in the sandbox environment](https://github.com/department-of-veterans-affairs/vets-api-clients/blob/master/test_accounts.md). Sandbox test data and test users for the Benefits Claims API are valid for all versions of the API.\n" + "description": "## Background\n\nThe Benefits Claims API Version 2 lets internal consumers: \n\n- Retrieve existing claim information, including status, by claim ID\n- Automatically establish an Intent To File (21-0966) in VBMS\n- Automatically establish a disability compensation claim (21-526EZ) in VBMS\n- Digitally submit supporting documentation for disability compensation claims\n- Retrieve the active Power of Attorney organization of individual with power of attorney for a Veteran\n- Automatically establish a power of attorney appointment in VBMS for an accredited organization (VA Form 21-22).\n- Automatically establish a power of attorney appointment in VBMS for an accredited individual (VA Form 21-22a).\n\nYou should use the [Benefits Claims API Version 1](https://developer.va.gov/explore/benefits/docs/claims?version=current) if you are a consumer outside of VA and do not have the necessary VA agreements to use this API.\n \n## Technical Overview\n\nThis API accepts a payload of requests and responses with the payload identifying the claim and Veteran. Responses provide the submission’s processing status. Responses also provide a unique ID which can be used with the appropriate GET endpoint to return detailed, end-to-end claims status tracking. \n\nEnd-to-end claims tracking provides the status of claims as they move through the submission process, but does not return whether the claim was approved or denied. \n\n### Claim statuses\n\nClaims are first submitted by this API and then established in Veterans Benefits Management System (VBMS). A 200 response means that the claim was successfully submitted by the API. It does not mean VA has received the claim. Use the appropriate GET endpoint and the ID returned with your submission response to confirm the status of the submission. Statuses are:\n\n- Pending - the claim is successfully submitted for processing\n- Errored - the submission encountered upstream errors\n- Canceled - the claim was identified as a duplicate or another issue caused the claim to be canceled. For duplicate claims, the tracking of the claim's progress happens under a different Claim ID . \n\nOther statuses this API returns align with the [VA.gov](http://va.gov/) [claim status descriptions](https://www.va.gov/resources/what-your-claim-status-means/), which are:\n\n- Claim received\n- Initial review\n- Evidence gathering, review, and decision\n- Preparation for notification\n- Complete\n\n### Finding a Veteran's unique VA ID\n\nThis API uses a unique Veteran identifier to identify the subject of each API request. This Veteran identifier can be retrieved by passing the Veteran’s first name, last name, DOB, and SSN to the ‘/veteran-id’ endpoint. This identifier should then be used as the Veteran ID parameter in request URLs.\n\nNote: though Veteran identifiers are typically static, they may change over time. If a specific Veteran ID suddenly responds with a ‘404 not found’ error, the identifier may have changed. It’s a good idea to periodically check the identifier for each Veteran.\n\n### Authentication and authorization\n\nThe authentication model for the Benefits Claims Version 2 is based on OAuth 2.0 / OpenID Connect and supports the [client credentials grant](https://developer.va.gov/explore/authorization/docs/client-credentials?api=claims).\n\n**Important**: To get production access, you must either work for VA or have specific VA agreements in place. If you have questions, [contact us](https://developer.va.gov/support/contact-us).\n\n### Test data for sandbox environment use\n\nWe use mock [test data in the sandbox environment](https://github.com/department-of-veterans-affairs/vets-api-clients/blob/master/test_accounts.md). Sandbox test data and test users for the Benefits Claims API are valid for all versions of the API.\n" }, "tags": [ { @@ -28,7 +28,7 @@ }, { "name": "Power of Attorney", - "description": "Allows authenticated and authorized users to retrieve the active power of attorney for a Veteran\n" + "description": "Allows authenticated and authorized users to automatically establish power of attorney appointments to an organization or an individual. Organizations and individuals must be VA accredited representatives.\n" } ], "components": { @@ -5720,7 +5720,7 @@ "application/json": { "example": { "data": { - "id": "450bd9ed-ec42-414a-a338-d79c766f7c8e", + "id": "8b3c6607-078b-419b-8549-726da40193df", "type": "forms/526", "attributes": { "veteran": { @@ -5871,7 +5871,7 @@ "status": "404", "detail": "Resource not found", "source": { - "pointer": "/modules/claims_api/app/controllers/claims_api/v2/veterans/disability_compensation_controller.rb:66:in `attachments'" + "pointer": "/modules/claims_api/app/controllers/claims_api/v2/veterans/disability_compensation_controller.rb:70:in `attachments'" } } ] @@ -7804,8 +7804,8 @@ "id": "1", "type": "intent_to_file", "attributes": { - "creationDate": "2024-02-12", - "expirationDate": "2025-02-12", + "creationDate": "2024-03-26", + "expirationDate": "2025-03-26", "type": "compensation", "status": "active" } @@ -8524,7 +8524,2910 @@ "status": "422", "detail": "Could not retrieve Power of Attorney due to multiple representatives with code: A1Q", "source": { - "pointer": "/modules/claims_api/app/controllers/claims_api/v2/veterans/power_of_attorney_controller.rb:108:in `representative'" + "pointer": "/modules/claims_api/app/controllers/claims_api/v2/veterans/power_of_attorney/base_controller.rb:112:in `representative'" + } + } + ] + }, + "schema": { + "required": [ + "errors" + ], + "properties": { + "errors": { + "type": "array", + "items": { + "additionalProperties": false, + "required": [ + "title", + "detail" + ], + "properties": { + "title": { + "type": "string", + "description": "HTTP error title" + }, + "detail": { + "type": "string", + "description": "HTTP error detail" + }, + "status": { + "type": "string", + "description": "HTTP error status code" + }, + "source": { + "type": "object", + "additionalProperties": false, + "description": "Source of error", + "properties": { + "pointer": { + "type": "string", + "description": "Pointer to source of error" + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/veterans/{veteranId}/2122a": { + "post": { + "summary": "Appoint an individual Power of Attorney for a Veteran.", + "tags": [ + "Power of Attorney" + ], + "operationId": "post2122a", + "security": [ + { + "productionOauth": [ + "system/claim.read", + "system/claim.write" + ] + }, + { + "sandboxOauth": [ + "system/claim.read", + "system/claim.write" + ] + }, + { + "bearer_token": [ + + ] + } + ], + "parameters": [ + { + "name": "veteranId", + "in": "path", + "required": true, + "example": "1012667145V762142", + "description": "ID of Veteran", + "schema": { + "type": "string" + } + } + ], + "description": "Updates current Power of Attorney for Veteran.", + "responses": { + "202": { + "description": "Valid request response", + "content": { + "application/json": { + "example": { + "data": { + "id": "9a9b6db5-abfc-45f3-ab60-785e3fb052ed", + "type": "individual", + "attributes": { + "code": "083", + "name": "Firstname Lastname", + "phoneNumber": "555-555-5555" + } + } + }, + "schema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "attributes" + ], + "properties": { + "type": { + "type": "string" + }, + "id": { + "type": "string" + }, + "attributes": { + "type": "object", + "additionalProperties": false, + "required": [ + "code" + ], + "properties": { + "code": { + "type": "string", + "description": "code for Power of attorney" + }, + "phoneNumber": { + "type": "string" + }, + "name": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "example": { + "errors": [ + { + "title": "Not authorized", + "status": "401", + "detail": "Not authorized" + } + ] + }, + "schema": { + "required": [ + "errors" + ], + "properties": { + "errors": { + "type": "array", + "items": { + "additionalProperties": false, + "required": [ + "title", + "detail" + ], + "properties": { + "title": { + "type": "string", + "description": "HTTP error title" + }, + "detail": { + "type": "string", + "description": "HTTP error detail" + }, + "status": { + "type": "string", + "description": "HTTP error status code" + }, + "source": { + "type": "object", + "additionalProperties": false, + "description": "Source of error", + "properties": { + "pointer": { + "type": "string", + "description": "Pointer to source of error" + } + } + } + } + } + } + } + } + } + } + }, + "422": { + "description": "Unprocessable Entity", + "content": { + "application/json": { + "example": { + "errors": [ + { + "title": "Unprocessable entity", + "detail": "The property /representative did not contain the required key poaCode", + "status": "422", + "source": { + "pointer": "data/attributes/representative" + } + }, + { + "title": "Unprocessable entity", + "detail": "The property / did not contain the required key veteran", + "status": "422", + "source": { + "pointer": "data/attributes/" + } + } + ] + }, + "schema": { + "required": [ + "errors" + ], + "properties": { + "errors": { + "type": "array", + "items": { + "additionalProperties": false, + "required": [ + "title", + "detail" + ], + "properties": { + "title": { + "type": "string", + "description": "HTTP error title" + }, + "detail": { + "type": "string", + "description": "HTTP error detail" + }, + "status": { + "type": "string", + "description": "HTTP error status code" + }, + "source": { + "type": "object", + "additionalProperties": false, + "description": "Source of error", + "properties": { + "pointer": { + "type": "string", + "description": "Pointer to source of error" + } + } + } + } + } + } + } + } + } + } + }, + "404": { + "description": "Resource not found", + "content": { + "application/json": { + "example": { + "errors": [ + { + "title": "Resource not found", + "status": "404", + "detail": "Could not find an Accredited Representative with code: 083", + "source": { + "pointer": "/modules/claims_api/app/controllers/claims_api/v2/veterans/power_of_attorney/individual_controller.rb:35:in `validate_individual_poa_code!'" + } + } + ] + }, + "schema": { + "required": [ + "errors" + ], + "properties": { + "errors": { + "type": "array", + "items": { + "additionalProperties": false, + "required": [ + "title", + "detail" + ], + "properties": { + "title": { + "type": "string", + "description": "HTTP error title" + }, + "detail": { + "type": "string", + "description": "HTTP error detail" + }, + "status": { + "type": "string", + "description": "HTTP error status code" + }, + "source": { + "type": "object", + "additionalProperties": false, + "description": "Source of error", + "properties": { + "pointer": { + "type": "string", + "description": "Pointer to source of error" + } + } + } + } + } + } + } + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "type": "object", + "required": [ + "attributes", + null + ], + "properties": { + "attributes": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Form 2122a Schema", + "type": "object", + "additionalProperties": false, + "required": [ + "veteran", + "representative" + ], + "properties": { + "veteran": { + "type": "object", + "additionalProperties": false, + "required": [ + "address" + ], + "properties": { + "serviceNumber": { + "description": "The Veteran's Service Number", + "type": "string", + "maxLength": 9 + }, + "serviceBranch": { + "description": "Service Branch for the veteran.", + "type": "string", + "enum": [ + "AIR_FORCE", + "ARMY", + "COAST_GUARD", + "MARINE_CORPS", + "NAVY", + "SPACE_FORCE", + "OTHER" + ], + "example": "ARMY" + }, + "serviceBranchOther": { + "description": "For a 'service branch' of value 'other', please provide the service branch name.", + "type": "string", + "maxLength": 27, + "example": "Air National Guard" + }, + "address": { + "type": "object", + "additionalProperties": false, + "required": [ + "addressLine1", + "city", + "stateCode", + "country", + "zipCode" + ], + "properties": { + "addressLine1": { + "description": "Street address with number and name.", + "type": "string", + "pattern": "^([-a-zA-Z0-9'.,&#]([-a-zA-Z0-9'.,&# ])?)+$", + "maxLength": 30 + }, + "addressLine2": { + "type": "string", + "maxLength": 5 + }, + "city": { + "description": "City for the address.", + "type": "string", + "example": "Portland", + "maxLength": 18 + }, + "stateCode": { + "description": "State for the address.", + "type": "string", + "pattern": "^[a-z,A-Z]{2}$", + "example": "OR" + }, + "country": { + "description": "Country of the address.", + "type": "string", + "example": "USA" + }, + "zipCode": { + "description": "Zipcode (First 5 digits) of the address.", + "type": "string", + "pattern": "^\\d{5}?$", + "example": "12345" + }, + "zipCodeSuffix": { + "description": "Zipcode (Last 4 digits) of the address.", + "type": "string", + "pattern": "^\\d{4}?$", + "example": "6789" + } + } + }, + "phone": { + "$comment": "the phone fields must not exceed 20 chars, when concatenated", + "type": "object", + "additionalProperties": false, + "required": [ + "areaCode", + "phoneNumber" + ], + "properties": { + "countryCode": { + "type": "string", + "pattern": "^[0-9]+$" + }, + "areaCode": { + "description": "Area code of the phone number.", + "type": "string", + "pattern": "^[2-9][0-9]{2}$", + "example": "555" + }, + "phoneNumber": { + "description": "Phone number.", + "type": "string", + "pattern": "^[0-9]{1,14}$", + "example": "555-5555" + }, + "phoneNumberExt": { + "type": "string", + "pattern": "^[a-zA-Z0-9]{1,10}$" + } + } + }, + "email": { + "description": "Email address of the veteran.", + "type": "string", + "pattern": ".@.", + "maxLength": 61, + "example": "veteran@example.com" + } + } + }, + "claimant": { + "type": "object", + "additionalProperties": false, + "properties": { + "claimantId": { + "type": "string", + "example": "123456789", + "description": "Id of the claimant." + }, + "address": { + "type": "object", + "additionalProperties": false, + "properties": { + "addressLine1": { + "description": "Street address with number and name. Required if claimant information provided.", + "type": "string", + "pattern": "^([-a-zA-Z0-9'.,&#]([-a-zA-Z0-9'.,&# ])?)+$", + "maxLength": 30 + }, + "addressLine2": { + "type": "string", + "maxLength": 5 + }, + "city": { + "description": "City for the address. Required if claimant information provided.", + "type": "string", + "example": "Portland", + "maxLength": 18 + }, + "stateCode": { + "description": "State for the address. Required if claimant information provided.", + "type": "string", + "pattern": "^[a-z,A-Z]{2}$", + "example": "OR" + }, + "country": { + "description": "Country of the address. Required if claimant information provided.", + "type": "string", + "example": "USA" + }, + "zipCode": { + "description": "Zipcode (First 5 digits) of the address. Required if claimant information provided.", + "type": "string", + "pattern": "^\\d{5}?$", + "example": "12345" + }, + "zipCodeSuffix": { + "description": "Zipcode (Last 4 digits) of the address.", + "type": "string", + "pattern": "^\\d{4}?$", + "example": "6789" + } + } + }, + "phone": { + "$comment": "the phone fields must not exceed 20 chars, when concatenated", + "type": "object", + "additionalProperties": false, + "required": [ + "areaCode", + "phoneNumber" + ], + "properties": { + "countryCode": { + "type": "string", + "pattern": "^[0-9]+$" + }, + "areaCode": { + "description": "Area code of the phone number.", + "type": "string", + "pattern": "^[2-9][0-9]{2}$", + "example": "555" + }, + "phoneNumber": { + "description": "Phone number.", + "type": "string", + "pattern": "^[0-9]{1,14}$", + "example": "555-5555" + }, + "phoneNumberExt": { + "type": "string", + "pattern": "^[a-zA-Z0-9]{1,10}$" + } + } + }, + "email": { + "description": "Email address of the claimant.", + "type": "string", + "pattern": ".@.", + "maxLength": 61, + "example": "claimant@example.com" + }, + "relationship": { + "description": "Relationship of claimant to the veteran. Required if claimant information provided.", + "type": "string", + "example": "Spouse" + } + } + }, + "representative": { + "description": "Details of the individual representative representing the veteran.", + "type": "object", + "additionalProperties": false, + "required": [ + "poaCode", + "firstName", + "lastName", + "type" + ], + "properties": { + "poaCode": { + "description": "The POA code of the representative.", + "type": "string", + "example": "A1Q" + }, + "firstName": { + "description": "First Name of the representative.", + "type": "string", + "example": "John" + }, + "lastName": { + "description": "Last Name of the representative", + "type": "string", + "example": "Doe" + }, + "type": { + "description": "Type of individual representative", + "type": "string", + "enum": [ + "ATTORNEY", + "AGENT" + ], + "example": "ATTORNEY" + }, + "address": { + "type": "object", + "additionalProperties": false, + "properties": { + "addressLine1": { + "description": "Street address with number and name.", + "type": "string", + "pattern": "^([-a-zA-Z0-9'.,&#]([-a-zA-Z0-9'.,&# ])?)+$", + "maxLength": 30 + }, + "addressLine2": { + "type": "string", + "maxLength": 5 + }, + "city": { + "description": "City for the address.", + "type": "string", + "example": "Portland", + "maxLength": 18 + }, + "stateCode": { + "description": "State for the address.", + "type": "string", + "pattern": "^[a-z,A-Z]{2}$", + "example": "OR" + }, + "country": { + "description": "Country of the address.", + "type": "string", + "example": "USA" + }, + "zipCode": { + "description": "Zipcode (First 5 digits) of the address.", + "type": "string", + "pattern": "^\\d{5}?$", + "example": "12345" + }, + "zipCodeSuffix": { + "description": "Zipcode (Last 4 digits) of the address.", + "type": "string", + "pattern": "^\\d{4}?$", + "example": "6789" + } + } + }, + "organizationName": { + "description": "Name of the service organization.", + "type": "string", + "example": "I help vets LLC." + } + } + }, + "recordConsent": { + "description": "AUTHORIZATION FOR REPRESENTATIVE'S ACCESS TO RECORDS PROTECTED BY SECTION 7332, TITLE 38, U.S.C.", + "type": "boolean" + }, + "consentLimits": { + "description": "Consent in Item 19 for the disclosure of records relating to treatment for drug abuse, alcoholism or alcohol abuse, infection with the human immunodeficiency virus (HIV), or sickle cell anemia is limited as follows.", + "type": "array", + "items": { + "type": "string", + "enum": [ + "DRUG_ABUSE", + "ALCOHOLISM", + "HIV", + "SICKLE_CELL" + ] + }, + "example": "DRUG ABUSE" + }, + "consentAddressChange": { + "description": "AUTHORIZATION FOR REPRESENTATIVE TO ACT ON CLAIMANT'S BEHALF TO CHANGE CLAIMANT'S ADDRESS.", + "type": "boolean" + }, + "conditionsOfAppointment": { + "description": "If the individual named in Item 15A is an accredited agent or attorney, the scope of representation provided before VA may be limited by the agent or attorney as indicated below in Item 23", + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + }, + "example": { + "data": { + "attributes": { + "veteran": { + "address": { + "addressLine1": "123", + "addressLine2": "2a", + "city": "city", + "country": "US", + "stateCode": "OR", + "zipCode": "12345", + "zipCodeSuffix": "6789" + } + }, + "representative": { + "poaCode": "083", + "firstName": "my", + "lastName": "name", + "type": "ATTORNEY", + "address": { + "addressLine1": "123", + "addressLine2": "2a", + "city": "city", + "country": "US", + "stateCode": "OR", + "zipCode": "12345", + "zipCodeSuffix": "6789" + } + } + } + } + } + } + } + }, + "required": true + } + } + }, + "/veterans/{veteranId}/2122": { + "post": { + "summary": "Appoint an organization Power of Attorney for a Veteran.", + "description": "Updates current Power of Attorney for Veteran.", + "tags": [ + "Power of Attorney" + ], + "operationId": "post2122", + "security": [ + { + "productionOauth": [ + "system/claim.read", + "system/claim.write" + ] + }, + { + "sandboxOauth": [ + "system/claim.read", + "system/claim.write" + ] + }, + { + "bearer_token": [ + + ] + } + ], + "parameters": [ + { + "name": "veteranId", + "in": "path", + "required": true, + "example": "1012667145V762142", + "description": "ID of Veteran", + "schema": { + "type": "string" + } + } + ], + "responses": { + "202": { + "description": "Valid request response", + "content": { + "application/json": { + "example": { + "data": { + "id": "618d9ba4-44cf-490a-bd56-8012b59b30e7", + "type": "organization", + "attributes": { + "code": "083", + "name": "083 - DISABLED AMERICAN VETERANS", + "phoneNumber": "555-555-5555" + } + } + }, + "schema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "attributes" + ], + "properties": { + "type": { + "type": "string" + }, + "id": { + "type": "string" + }, + "attributes": { + "type": "object", + "additionalProperties": false, + "required": [ + "code" + ], + "properties": { + "code": { + "type": "string", + "description": "code for Power of attorney" + }, + "phoneNumber": { + "type": "string" + }, + "name": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "example": { + "errors": [ + { + "title": "Not authorized", + "status": "401", + "detail": "Not authorized" + } + ] + }, + "schema": { + "required": [ + "errors" + ], + "properties": { + "errors": { + "type": "array", + "items": { + "additionalProperties": false, + "required": [ + "title", + "detail" + ], + "properties": { + "title": { + "type": "string", + "description": "HTTP error title" + }, + "detail": { + "type": "string", + "description": "HTTP error detail" + }, + "status": { + "type": "string", + "description": "HTTP error status code" + }, + "source": { + "type": "object", + "additionalProperties": false, + "description": "Source of error", + "properties": { + "pointer": { + "type": "string", + "description": "Pointer to source of error" + } + } + } + } + } + } + } + } + } + } + }, + "422": { + "description": "Unprocessable Entity", + "content": { + "application/json": { + "example": { + "errors": [ + { + "title": "Unprocessable entity", + "detail": "The property /serviceOrganization did not contain the required key poaCode", + "status": "422", + "source": { + "pointer": "data/attributes/serviceOrganization" + } + }, + { + "title": "Unprocessable entity", + "detail": "The property / did not contain the required key veteran", + "status": "422", + "source": { + "pointer": "data/attributes/" + } + } + ] + }, + "schema": { + "required": [ + "errors" + ], + "properties": { + "errors": { + "type": "array", + "items": { + "additionalProperties": false, + "required": [ + "title", + "detail" + ], + "properties": { + "title": { + "type": "string", + "description": "HTTP error title" + }, + "detail": { + "type": "string", + "description": "HTTP error detail" + }, + "status": { + "type": "string", + "description": "HTTP error status code" + }, + "source": { + "type": "object", + "additionalProperties": false, + "description": "Source of error", + "properties": { + "pointer": { + "type": "string", + "description": "Pointer to source of error" + } + } + } + } + } + } + } + } + } + } + }, + "404": { + "description": "Resource not found", + "content": { + "application/json": { + "example": { + "errors": [ + { + "title": "Resource not found", + "status": "404", + "detail": "Could not find an Organization with code: 083", + "source": { + "pointer": "/modules/claims_api/app/controllers/claims_api/v2/veterans/power_of_attorney/organization_controller.rb:35:in `validate_org_poa_code!'" + } + } + ] + }, + "schema": { + "required": [ + "errors" + ], + "properties": { + "errors": { + "type": "array", + "items": { + "additionalProperties": false, + "required": [ + "title", + "detail" + ], + "properties": { + "title": { + "type": "string", + "description": "HTTP error title" + }, + "detail": { + "type": "string", + "description": "HTTP error detail" + }, + "status": { + "type": "string", + "description": "HTTP error status code" + }, + "source": { + "type": "object", + "additionalProperties": false, + "description": "Source of error", + "properties": { + "pointer": { + "type": "string", + "description": "Pointer to source of error" + } + } + } + } + } + } + } + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "type": "object", + "required": [ + "attributes", + null + ], + "properties": { + "attributes": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Form 2122 Schema", + "type": "object", + "additionalProperties": false, + "required": [ + "veteran", + "serviceOrganization" + ], + "properties": { + "veteran": { + "type": "object", + "additionalProperties": false, + "required": [ + "address" + ], + "properties": { + "address": { + "type": "object", + "additionalProperties": false, + "required": [ + "addressLine1", + "city", + "country", + "stateCode", + "zipCode" + ], + "properties": { + "addressLine1": { + "description": "Street address with number and name.", + "type": "string", + "pattern": "^([-a-zA-Z0-9'.,&#]([-a-zA-Z0-9'.,&# ])?)+$", + "maxLength": 30 + }, + "addressLine2": { + "type": "string", + "maxLength": 5 + }, + "city": { + "description": "City for the address.", + "type": "string", + "example": "Portland", + "maxLength": 18 + }, + "stateCode": { + "description": "State for the address.", + "type": "string", + "pattern": "^[a-z,A-Z]{2}$", + "example": "OR" + }, + "country": { + "description": "Country of the address.", + "type": "string", + "example": "USA" + }, + "zipCode": { + "description": "Zipcode (First 5 digits) of the address.", + "type": "string", + "pattern": "^\\d{5}?$", + "example": "12345" + }, + "zipCodeSuffix": { + "description": "Zipcode (Last 4 digits) of the address.", + "type": "string", + "pattern": "^\\d{4}?$", + "example": "6789" + } + } + }, + "phone": { + "$comment": "the phone fields must not exceed 20 chars, when concatenated", + "type": "object", + "additionalProperties": false, + "required": [ + "areaCode", + "phoneNumber" + ], + "properties": { + "countryCode": { + "type": "string", + "pattern": "^[0-9]+$" + }, + "areaCode": { + "description": "Area code of the phone number.", + "type": "string", + "pattern": "^[2-9][0-9]{2}$", + "example": "555" + }, + "phoneNumber": { + "description": "Phone number.", + "type": "string", + "pattern": "^[0-9]{1,14}$", + "example": "555-5555" + }, + "phoneNumberExt": { + "type": "string", + "pattern": "^[a-zA-Z0-9]{1,10}$" + } + } + }, + "email": { + "description": "Email address of the veteran.", + "type": "string", + "pattern": ".@.", + "maxLength": 61, + "example": "veteran@example.com" + }, + "serviceNumber": { + "description": "Service number for the veteran.", + "type": "string", + "pattern": "^\\d{9}?$", + "example": "123456789" + } + } + }, + "claimant": { + "type": "object", + "additionalProperties": false, + "properties": { + "claimantId": { + "type": "string", + "example": "123456789", + "description": "Id of the claimant." + }, + "address": { + "type": "object", + "additionalProperties": false, + "properties": { + "addressLine1": { + "description": "Street address with number and name. Required if claimant information provided.", + "type": "string", + "pattern": "^([-a-zA-Z0-9'.,&#]([-a-zA-Z0-9'.,&# ])?)+$", + "maxLength": 30 + }, + "addressLine2": { + "type": "string", + "maxLength": 5 + }, + "city": { + "description": "City for the address. Required if claimant information provided.", + "type": "string", + "example": "Portland", + "maxLength": 18 + }, + "stateCode": { + "description": "State for the address. Required if claimant information provided.", + "type": "string", + "pattern": "^[a-z,A-Z]{2}$", + "example": "OR" + }, + "country": { + "description": "Country of the address. Required if claimant information provided.", + "type": "string", + "example": "USA" + }, + "zipCode": { + "description": "Zipcode (First 5 digits) of the address. Required if claimant information provided.", + "type": "string", + "pattern": "^\\d{5}?$", + "example": "12345" + }, + "zipCodeSuffix": { + "description": "Zipcode (Last 4 digits) of the address.", + "type": "string", + "pattern": "^\\d{4}?$", + "example": "6789" + }, + "additionalProperties": { + "type": "boolean" + } + } + }, + "phone": { + "$comment": "the phone fields must not exceed 20 chars, when concatenated", + "type": "object", + "additionalProperties": false, + "required": [ + "areaCode", + "phoneNumber" + ], + "properties": { + "countryCode": { + "type": "string", + "pattern": "^[0-9]+$" + }, + "areaCode": { + "description": "Area code of the phone number.", + "type": "string", + "pattern": "^[2-9][0-9]{2}$", + "example": "555" + }, + "phoneNumber": { + "description": "Phone number.", + "type": "string", + "pattern": "^[0-9]{1,14}$", + "example": "555-5555" + }, + "phoneNumberExt": { + "type": "string", + "pattern": "^[a-zA-Z0-9]{1,10}$" + } + } + }, + "email": { + "description": "Email address of the claimant.", + "type": "string", + "pattern": ".@.", + "maxLength": 61, + "example": "claimant@example.com" + }, + "relationship": { + "description": "Relationship of claimant to the veteran. Required if claimant information provided.", + "type": "string", + "example": "Spouse" + } + } + }, + "serviceOrganization": { + "description": "Details of the Service Organization representing the veteran.", + "type": "object", + "additionalProperties": false, + "required": [ + "poaCode" + ], + "properties": { + "poaCode": { + "description": "The POA code of the organization.", + "type": "string", + "example": "A1Q" + }, + "organizationName": { + "description": "Name of the service organization.", + "type": "string", + "example": "I help vets LLC." + }, + "firstName": { + "description": "First Name of the representative.", + "type": "string", + "example": "John" + }, + "lastName": { + "description": "Last Name of the representative", + "type": "string", + "example": "Doe" + }, + "jobTitle": { + "description": "Job title of the representative.", + "type": "string", + "example": "Veteran Service representative" + }, + "email": { + "description": "Email address of the service organization or representative.", + "type": "string", + "pattern": ".@.", + "maxLength": 61, + "example": "veteran_representative@example.com" + }, + "appointmentDate": { + "description": "Date of appointment with Veteran.", + "type": "string", + "pattern": "^(\\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$" + } + } + }, + "recordConsent": { + "description": "AUTHORIZATION FOR REPRESENTATIVE'S ACCESS TO RECORDS PROTECTED BY SECTION 7332, TITLE 38, U.S.C.", + "type": "boolean" + }, + "consentLimits": { + "description": "Consent in Item 19 for the disclosure of records relating to treatment for drug abuse, alcoholism or alcohol abuse, infection with the human immunodeficiency virus (HIV), or sickle cell anemia is limited as follows.", + "type": "array", + "items": { + "type": "string", + "enum": [ + "DRUG_ABUSE", + "ALCOHOLISM", + "HIV", + "SICKLE_CELL" + ] + }, + "example": "DRUG_ABUSE" + }, + "consentAddressChange": { + "description": "AUTHORIZATION FOR REPRESENTATIVE TO ACT ON CLAIMANT'S BEHALF TO CHANGE CLAIMANT'S ADDRESS.", + "type": "boolean" + } + } + } + } + } + }, + "example": { + "data": { + "attributes": { + "veteran": { + "address": { + "addressLine1": "123", + "city": "city", + "stateCode": "OR", + "country": "US", + "zipCode": "12345" + } + }, + "serviceOrganization": { + "poaCode": "083" + } + } + } + } + } + } + }, + "required": true + } + } + }, + "/veterans/{veteranId}/2122a/validate": { + "post": { + "summary": "Validates a 2122a form submission.", + "tags": [ + "Power of Attorney" + ], + "operationId": "post2122aValidate", + "security": [ + { + "productionOauth": [ + "system/claim.read", + "system/claim.write" + ] + }, + { + "sandboxOauth": [ + "system/claim.read", + "system/claim.write" + ] + }, + { + "bearer_token": [ + + ] + } + ], + "parameters": [ + { + "name": "veteranId", + "in": "path", + "required": true, + "example": "1012667145V762142", + "description": "ID of Veteran", + "schema": { + "type": "string" + } + } + ], + "description": "Validates a request appointing an individual as Power of Attorney (21-22a).\n", + "responses": { + "200": { + "description": "Valid request response", + "content": { + "application/json": { + "example": { + "data": { + "type": "form/21-22a/validation", + "attributes": { + "status": "valid" + } + } + }, + "schema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "attributes" + ], + "properties": { + "type": { + "type": "string" + }, + "attributes": { + "type": "object", + "additionalProperties": false, + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string", + "description": "Says if submission of 21-22a would work with the given parameters", + "enum": [ + "valid" + ] + } + } + } + } + } + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "example": { + "errors": [ + { + "title": "Not authorized", + "status": "401", + "detail": "Not authorized" + } + ] + }, + "schema": { + "required": [ + "errors" + ], + "properties": { + "errors": { + "type": "array", + "items": { + "additionalProperties": false, + "required": [ + "title", + "detail" + ], + "properties": { + "title": { + "type": "string", + "description": "HTTP error title" + }, + "detail": { + "type": "string", + "description": "HTTP error detail" + }, + "status": { + "type": "string", + "description": "HTTP error status code" + }, + "source": { + "type": "object", + "additionalProperties": false, + "description": "Source of error", + "properties": { + "pointer": { + "type": "string", + "description": "Pointer to source of error" + } + } + } + } + } + } + } + } + } + } + }, + "422": { + "description": "Unprocessable Entity", + "content": { + "application/json": { + "example": { + "errors": [ + { + "title": "Unprocessable entity", + "detail": "The property /representative did not contain the required key poaCode", + "status": "422", + "source": { + "pointer": "data/attributes/representative" + } + }, + { + "title": "Unprocessable entity", + "detail": "The property / did not contain the required key veteran", + "status": "422", + "source": { + "pointer": "data/attributes/" + } + } + ] + }, + "schema": { + "required": [ + "errors" + ], + "properties": { + "errors": { + "type": "array", + "items": { + "additionalProperties": false, + "required": [ + "title", + "detail" + ], + "properties": { + "title": { + "type": "string", + "description": "HTTP error title" + }, + "detail": { + "type": "string", + "description": "HTTP error detail" + }, + "status": { + "type": "string", + "description": "HTTP error status code" + }, + "source": { + "type": "object", + "additionalProperties": false, + "description": "Source of error", + "properties": { + "pointer": { + "type": "string", + "description": "Pointer to source of error" + } + } + } + } + } + } + } + } + } + } + }, + "404": { + "description": "Resource not found", + "content": { + "application/json": { + "example": { + "errors": [ + { + "title": "Resource not found", + "status": "404", + "detail": "Could not find an Accredited Representative with code: 083", + "source": { + "pointer": "/modules/claims_api/app/controllers/claims_api/v2/veterans/power_of_attorney/individual_controller.rb:35:in `validate_individual_poa_code!'" + } + } + ] + }, + "schema": { + "required": [ + "errors" + ], + "properties": { + "errors": { + "type": "array", + "items": { + "additionalProperties": false, + "required": [ + "title", + "detail" + ], + "properties": { + "title": { + "type": "string", + "description": "HTTP error title" + }, + "detail": { + "type": "string", + "description": "HTTP error detail" + }, + "status": { + "type": "string", + "description": "HTTP error status code" + }, + "source": { + "type": "object", + "additionalProperties": false, + "description": "Source of error", + "properties": { + "pointer": { + "type": "string", + "description": "Pointer to source of error" + } + } + } + } + } + } + } + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "type": "object", + "required": [ + "attributes", + null + ], + "properties": { + "attributes": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Form 2122a Schema", + "type": "object", + "additionalProperties": false, + "required": [ + "veteran", + "representative" + ], + "properties": { + "veteran": { + "type": "object", + "additionalProperties": false, + "required": [ + "address" + ], + "properties": { + "serviceNumber": { + "description": "The Veteran's Service Number", + "type": "string", + "maxLength": 9 + }, + "serviceBranch": { + "description": "Service Branch for the veteran.", + "type": "string", + "enum": [ + "AIR_FORCE", + "ARMY", + "COAST_GUARD", + "MARINE_CORPS", + "NAVY", + "SPACE_FORCE", + "OTHER" + ], + "example": "ARMY" + }, + "serviceBranchOther": { + "description": "For a 'service branch' of value 'other', please provide the service branch name.", + "type": "string", + "maxLength": 27, + "example": "Air National Guard" + }, + "address": { + "type": "object", + "additionalProperties": false, + "required": [ + "addressLine1", + "city", + "stateCode", + "country", + "zipCode" + ], + "properties": { + "addressLine1": { + "description": "Street address with number and name.", + "type": "string", + "pattern": "^([-a-zA-Z0-9'.,&#]([-a-zA-Z0-9'.,&# ])?)+$", + "maxLength": 30 + }, + "addressLine2": { + "type": "string", + "maxLength": 5 + }, + "city": { + "description": "City for the address.", + "type": "string", + "example": "Portland", + "maxLength": 18 + }, + "stateCode": { + "description": "State for the address.", + "type": "string", + "pattern": "^[a-z,A-Z]{2}$", + "example": "OR" + }, + "country": { + "description": "Country of the address.", + "type": "string", + "example": "USA" + }, + "zipCode": { + "description": "Zipcode (First 5 digits) of the address.", + "type": "string", + "pattern": "^\\d{5}?$", + "example": "12345" + }, + "zipCodeSuffix": { + "description": "Zipcode (Last 4 digits) of the address.", + "type": "string", + "pattern": "^\\d{4}?$", + "example": "6789" + } + } + }, + "phone": { + "$comment": "the phone fields must not exceed 20 chars, when concatenated", + "type": "object", + "additionalProperties": false, + "required": [ + "areaCode", + "phoneNumber" + ], + "properties": { + "countryCode": { + "type": "string", + "pattern": "^[0-9]+$" + }, + "areaCode": { + "description": "Area code of the phone number.", + "type": "string", + "pattern": "^[2-9][0-9]{2}$", + "example": "555" + }, + "phoneNumber": { + "description": "Phone number.", + "type": "string", + "pattern": "^[0-9]{1,14}$", + "example": "555-5555" + }, + "phoneNumberExt": { + "type": "string", + "pattern": "^[a-zA-Z0-9]{1,10}$" + } + } + }, + "email": { + "description": "Email address of the veteran.", + "type": "string", + "pattern": ".@.", + "maxLength": 61, + "example": "veteran@example.com" + } + } + }, + "claimant": { + "type": "object", + "additionalProperties": false, + "properties": { + "claimantId": { + "type": "string", + "example": "123456789", + "description": "Id of the claimant." + }, + "address": { + "type": "object", + "additionalProperties": false, + "properties": { + "addressLine1": { + "description": "Street address with number and name. Required if claimant information provided.", + "type": "string", + "pattern": "^([-a-zA-Z0-9'.,&#]([-a-zA-Z0-9'.,&# ])?)+$", + "maxLength": 30 + }, + "addressLine2": { + "type": "string", + "maxLength": 5 + }, + "city": { + "description": "City for the address. Required if claimant information provided.", + "type": "string", + "example": "Portland", + "maxLength": 18 + }, + "stateCode": { + "description": "State for the address. Required if claimant information provided.", + "type": "string", + "pattern": "^[a-z,A-Z]{2}$", + "example": "OR" + }, + "country": { + "description": "Country of the address. Required if claimant information provided.", + "type": "string", + "example": "USA" + }, + "zipCode": { + "description": "Zipcode (First 5 digits) of the address. Required if claimant information provided.", + "type": "string", + "pattern": "^\\d{5}?$", + "example": "12345" + }, + "zipCodeSuffix": { + "description": "Zipcode (Last 4 digits) of the address.", + "type": "string", + "pattern": "^\\d{4}?$", + "example": "6789" + } + } + }, + "phone": { + "$comment": "the phone fields must not exceed 20 chars, when concatenated", + "type": "object", + "additionalProperties": false, + "required": [ + "areaCode", + "phoneNumber" + ], + "properties": { + "countryCode": { + "type": "string", + "pattern": "^[0-9]+$" + }, + "areaCode": { + "description": "Area code of the phone number.", + "type": "string", + "pattern": "^[2-9][0-9]{2}$", + "example": "555" + }, + "phoneNumber": { + "description": "Phone number.", + "type": "string", + "pattern": "^[0-9]{1,14}$", + "example": "555-5555" + }, + "phoneNumberExt": { + "type": "string", + "pattern": "^[a-zA-Z0-9]{1,10}$" + } + } + }, + "email": { + "description": "Email address of the claimant.", + "type": "string", + "pattern": ".@.", + "maxLength": 61, + "example": "claimant@example.com" + }, + "relationship": { + "description": "Relationship of claimant to the veteran. Required if claimant information provided.", + "type": "string", + "example": "Spouse" + } + } + }, + "representative": { + "description": "Details of the individual representative representing the veteran.", + "type": "object", + "additionalProperties": false, + "required": [ + "poaCode", + "firstName", + "lastName", + "type" + ], + "properties": { + "poaCode": { + "description": "The POA code of the representative.", + "type": "string", + "example": "A1Q" + }, + "firstName": { + "description": "First Name of the representative.", + "type": "string", + "example": "John" + }, + "lastName": { + "description": "Last Name of the representative", + "type": "string", + "example": "Doe" + }, + "type": { + "description": "Type of individual representative", + "type": "string", + "enum": [ + "ATTORNEY", + "AGENT" + ], + "example": "ATTORNEY" + }, + "address": { + "type": "object", + "additionalProperties": false, + "properties": { + "addressLine1": { + "description": "Street address with number and name.", + "type": "string", + "pattern": "^([-a-zA-Z0-9'.,&#]([-a-zA-Z0-9'.,&# ])?)+$", + "maxLength": 30 + }, + "addressLine2": { + "type": "string", + "maxLength": 5 + }, + "city": { + "description": "City for the address.", + "type": "string", + "example": "Portland", + "maxLength": 18 + }, + "stateCode": { + "description": "State for the address.", + "type": "string", + "pattern": "^[a-z,A-Z]{2}$", + "example": "OR" + }, + "country": { + "description": "Country of the address.", + "type": "string", + "example": "USA" + }, + "zipCode": { + "description": "Zipcode (First 5 digits) of the address.", + "type": "string", + "pattern": "^\\d{5}?$", + "example": "12345" + }, + "zipCodeSuffix": { + "description": "Zipcode (Last 4 digits) of the address.", + "type": "string", + "pattern": "^\\d{4}?$", + "example": "6789" + } + } + }, + "organizationName": { + "description": "Name of the service organization.", + "type": "string", + "example": "I help vets LLC." + } + } + }, + "recordConsent": { + "description": "AUTHORIZATION FOR REPRESENTATIVE'S ACCESS TO RECORDS PROTECTED BY SECTION 7332, TITLE 38, U.S.C.", + "type": "boolean" + }, + "consentLimits": { + "description": "Consent in Item 19 for the disclosure of records relating to treatment for drug abuse, alcoholism or alcohol abuse, infection with the human immunodeficiency virus (HIV), or sickle cell anemia is limited as follows.", + "type": "array", + "items": { + "type": "string", + "enum": [ + "DRUG_ABUSE", + "ALCOHOLISM", + "HIV", + "SICKLE_CELL" + ] + }, + "example": "DRUG ABUSE" + }, + "consentAddressChange": { + "description": "AUTHORIZATION FOR REPRESENTATIVE TO ACT ON CLAIMANT'S BEHALF TO CHANGE CLAIMANT'S ADDRESS.", + "type": "boolean" + }, + "conditionsOfAppointment": { + "description": "If the individual named in Item 15A is an accredited agent or attorney, the scope of representation provided before VA may be limited by the agent or attorney as indicated below in Item 23", + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + }, + "example": { + "data": { + "attributes": { + "veteran": { + "address": { + "addressLine1": "123", + "addressLine2": "2a", + "city": "city", + "country": "US", + "stateCode": "OR", + "zipCode": "12345", + "zipCodeSuffix": "6789" + } + }, + "representative": { + "poaCode": "083", + "firstName": "my", + "lastName": "name", + "type": "ATTORNEY", + "address": { + "addressLine1": "123", + "addressLine2": "2a", + "city": "city", + "country": "US", + "stateCode": "OR", + "zipCode": "12345", + "zipCodeSuffix": "6789" + } + } + } + } + } + } + } + }, + "required": true + } + } + }, + "/veterans/{veteranId}/2122/validate": { + "post": { + "summary": "Validates a 2122 form submission.", + "tags": [ + "Power of Attorney" + ], + "operationId": "post2122Validate", + "security": [ + { + "productionOauth": [ + "system/claim.read", + "system/claim.write" + ] + }, + { + "sandboxOauth": [ + "system/claim.read", + "system/claim.write" + ] + }, + { + "bearer_token": [ + + ] + } + ], + "parameters": [ + { + "name": "veteranId", + "in": "path", + "required": true, + "example": "1012667145V762142", + "description": "ID of Veteran", + "schema": { + "type": "string" + } + } + ], + "description": "Validates a request appointing an organization as Power of Attorney (21-22).\n", + "responses": { + "200": { + "description": "Valid request response", + "content": { + "application/json": { + "example": { + "data": { + "type": "form/21-22/validation", + "attributes": { + "status": "valid" + } + } + }, + "schema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "attributes" + ], + "properties": { + "type": { + "type": "string" + }, + "attributes": { + "type": "object", + "additionalProperties": false, + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string", + "description": "Says if submission of 21-22 would work with the given parameters", + "enum": [ + "valid" + ] + } + } + } + } + } + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "example": { + "errors": [ + { + "title": "Not authorized", + "status": "401", + "detail": "Not authorized" + } + ] + }, + "schema": { + "required": [ + "errors" + ], + "properties": { + "errors": { + "type": "array", + "items": { + "additionalProperties": false, + "required": [ + "title", + "detail" + ], + "properties": { + "title": { + "type": "string", + "description": "HTTP error title" + }, + "detail": { + "type": "string", + "description": "HTTP error detail" + }, + "status": { + "type": "string", + "description": "HTTP error status code" + }, + "source": { + "type": "object", + "additionalProperties": false, + "description": "Source of error", + "properties": { + "pointer": { + "type": "string", + "description": "Pointer to source of error" + } + } + } + } + } + } + } + } + } + } + }, + "422": { + "description": "Unprocessable Entity", + "content": { + "application/json": { + "example": { + "errors": [ + { + "title": "Unprocessable entity", + "detail": "The property /serviceOrganization did not contain the required key poaCode", + "status": "422", + "source": { + "pointer": "data/attributes/serviceOrganization" + } + }, + { + "title": "Unprocessable entity", + "detail": "The property / did not contain the required key veteran", + "status": "422", + "source": { + "pointer": "data/attributes/" + } + } + ] + }, + "schema": { + "required": [ + "errors" + ], + "properties": { + "errors": { + "type": "array", + "items": { + "additionalProperties": false, + "required": [ + "title", + "detail" + ], + "properties": { + "title": { + "type": "string", + "description": "HTTP error title" + }, + "detail": { + "type": "string", + "description": "HTTP error detail" + }, + "status": { + "type": "string", + "description": "HTTP error status code" + }, + "source": { + "type": "object", + "additionalProperties": false, + "description": "Source of error", + "properties": { + "pointer": { + "type": "string", + "description": "Pointer to source of error" + } + } + } + } + } + } + } + } + } + } + }, + "404": { + "description": "Resource not found", + "content": { + "application/json": { + "example": { + "errors": [ + { + "title": "Resource not found", + "status": "404", + "detail": "Could not find an Organization with code: 083", + "source": { + "pointer": "/modules/claims_api/app/controllers/claims_api/v2/veterans/power_of_attorney/organization_controller.rb:35:in `validate_org_poa_code!'" + } + } + ] + }, + "schema": { + "required": [ + "errors" + ], + "properties": { + "errors": { + "type": "array", + "items": { + "additionalProperties": false, + "required": [ + "title", + "detail" + ], + "properties": { + "title": { + "type": "string", + "description": "HTTP error title" + }, + "detail": { + "type": "string", + "description": "HTTP error detail" + }, + "status": { + "type": "string", + "description": "HTTP error status code" + }, + "source": { + "type": "object", + "additionalProperties": false, + "description": "Source of error", + "properties": { + "pointer": { + "type": "string", + "description": "Pointer to source of error" + } + } + } + } + } + } + } + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "type": "object", + "required": [ + "attributes", + null + ], + "properties": { + "attributes": { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Form 2122 Schema", + "type": "object", + "additionalProperties": false, + "required": [ + "veteran", + "serviceOrganization" + ], + "properties": { + "veteran": { + "type": "object", + "additionalProperties": false, + "required": [ + "address" + ], + "properties": { + "address": { + "type": "object", + "additionalProperties": false, + "required": [ + "addressLine1", + "city", + "country", + "stateCode", + "zipCode" + ], + "properties": { + "addressLine1": { + "description": "Street address with number and name.", + "type": "string", + "pattern": "^([-a-zA-Z0-9'.,&#]([-a-zA-Z0-9'.,&# ])?)+$", + "maxLength": 30 + }, + "addressLine2": { + "type": "string", + "maxLength": 5 + }, + "city": { + "description": "City for the address.", + "type": "string", + "example": "Portland", + "maxLength": 18 + }, + "stateCode": { + "description": "State for the address.", + "type": "string", + "pattern": "^[a-z,A-Z]{2}$", + "example": "OR" + }, + "country": { + "description": "Country of the address.", + "type": "string", + "example": "USA" + }, + "zipCode": { + "description": "Zipcode (First 5 digits) of the address.", + "type": "string", + "pattern": "^\\d{5}?$", + "example": "12345" + }, + "zipCodeSuffix": { + "description": "Zipcode (Last 4 digits) of the address.", + "type": "string", + "pattern": "^\\d{4}?$", + "example": "6789" + } + } + }, + "phone": { + "$comment": "the phone fields must not exceed 20 chars, when concatenated", + "type": "object", + "additionalProperties": false, + "required": [ + "areaCode", + "phoneNumber" + ], + "properties": { + "countryCode": { + "type": "string", + "pattern": "^[0-9]+$" + }, + "areaCode": { + "description": "Area code of the phone number.", + "type": "string", + "pattern": "^[2-9][0-9]{2}$", + "example": "555" + }, + "phoneNumber": { + "description": "Phone number.", + "type": "string", + "pattern": "^[0-9]{1,14}$", + "example": "555-5555" + }, + "phoneNumberExt": { + "type": "string", + "pattern": "^[a-zA-Z0-9]{1,10}$" + } + } + }, + "email": { + "description": "Email address of the veteran.", + "type": "string", + "pattern": ".@.", + "maxLength": 61, + "example": "veteran@example.com" + }, + "serviceNumber": { + "description": "Service number for the veteran.", + "type": "string", + "pattern": "^\\d{9}?$", + "example": "123456789" + } + } + }, + "claimant": { + "type": "object", + "additionalProperties": false, + "properties": { + "claimantId": { + "type": "string", + "example": "123456789", + "description": "Id of the claimant." + }, + "address": { + "type": "object", + "additionalProperties": false, + "properties": { + "addressLine1": { + "description": "Street address with number and name. Required if claimant information provided.", + "type": "string", + "pattern": "^([-a-zA-Z0-9'.,&#]([-a-zA-Z0-9'.,&# ])?)+$", + "maxLength": 30 + }, + "addressLine2": { + "type": "string", + "maxLength": 5 + }, + "city": { + "description": "City for the address. Required if claimant information provided.", + "type": "string", + "example": "Portland", + "maxLength": 18 + }, + "stateCode": { + "description": "State for the address. Required if claimant information provided.", + "type": "string", + "pattern": "^[a-z,A-Z]{2}$", + "example": "OR" + }, + "country": { + "description": "Country of the address. Required if claimant information provided.", + "type": "string", + "example": "USA" + }, + "zipCode": { + "description": "Zipcode (First 5 digits) of the address. Required if claimant information provided.", + "type": "string", + "pattern": "^\\d{5}?$", + "example": "12345" + }, + "zipCodeSuffix": { + "description": "Zipcode (Last 4 digits) of the address.", + "type": "string", + "pattern": "^\\d{4}?$", + "example": "6789" + }, + "additionalProperties": { + "type": "boolean" + } + } + }, + "phone": { + "$comment": "the phone fields must not exceed 20 chars, when concatenated", + "type": "object", + "additionalProperties": false, + "required": [ + "areaCode", + "phoneNumber" + ], + "properties": { + "countryCode": { + "type": "string", + "pattern": "^[0-9]+$" + }, + "areaCode": { + "description": "Area code of the phone number.", + "type": "string", + "pattern": "^[2-9][0-9]{2}$", + "example": "555" + }, + "phoneNumber": { + "description": "Phone number.", + "type": "string", + "pattern": "^[0-9]{1,14}$", + "example": "555-5555" + }, + "phoneNumberExt": { + "type": "string", + "pattern": "^[a-zA-Z0-9]{1,10}$" + } + } + }, + "email": { + "description": "Email address of the claimant.", + "type": "string", + "pattern": ".@.", + "maxLength": 61, + "example": "claimant@example.com" + }, + "relationship": { + "description": "Relationship of claimant to the veteran. Required if claimant information provided.", + "type": "string", + "example": "Spouse" + } + } + }, + "serviceOrganization": { + "description": "Details of the Service Organization representing the veteran.", + "type": "object", + "additionalProperties": false, + "required": [ + "poaCode" + ], + "properties": { + "poaCode": { + "description": "The POA code of the organization.", + "type": "string", + "example": "A1Q" + }, + "organizationName": { + "description": "Name of the service organization.", + "type": "string", + "example": "I help vets LLC." + }, + "firstName": { + "description": "First Name of the representative.", + "type": "string", + "example": "John" + }, + "lastName": { + "description": "Last Name of the representative", + "type": "string", + "example": "Doe" + }, + "jobTitle": { + "description": "Job title of the representative.", + "type": "string", + "example": "Veteran Service representative" + }, + "email": { + "description": "Email address of the service organization or representative.", + "type": "string", + "pattern": ".@.", + "maxLength": 61, + "example": "veteran_representative@example.com" + }, + "appointmentDate": { + "description": "Date of appointment with Veteran.", + "type": "string", + "pattern": "^(\\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$" + } + } + }, + "recordConsent": { + "description": "AUTHORIZATION FOR REPRESENTATIVE'S ACCESS TO RECORDS PROTECTED BY SECTION 7332, TITLE 38, U.S.C.", + "type": "boolean" + }, + "consentLimits": { + "description": "Consent in Item 19 for the disclosure of records relating to treatment for drug abuse, alcoholism or alcohol abuse, infection with the human immunodeficiency virus (HIV), or sickle cell anemia is limited as follows.", + "type": "array", + "items": { + "type": "string", + "enum": [ + "DRUG_ABUSE", + "ALCOHOLISM", + "HIV", + "SICKLE_CELL" + ] + }, + "example": "DRUG_ABUSE" + }, + "consentAddressChange": { + "description": "AUTHORIZATION FOR REPRESENTATIVE TO ACT ON CLAIMANT'S BEHALF TO CHANGE CLAIMANT'S ADDRESS.", + "type": "boolean" + } + } + } + } + } + }, + "example": { + "data": { + "attributes": { + "veteran": { + "address": { + "addressLine1": "123", + "city": "city", + "stateCode": "OR", + "country": "US", + "zipCode": "12345" + } + }, + "serviceOrganization": { + "poaCode": "083" + } + } + } + } + } + } + }, + "required": true + } + } + }, + "/veterans/{veteranId}/power-of-attorney/{id}": { + "get": { + "summary": "Checks status of Power of Attorney appointment form submission", + "description": "Gets the Power of Attorney appointment request status (21-22/21-22a)", + "tags": [ + "Power of Attorney" + ], + "operationId": "getPowerOfAttorneyStatus", + "security": [ + { + "productionOauth": [ + "system/claim.read", + "system/claim.write" + ] + }, + { + "sandboxOauth": [ + "system/claim.read", + "system/claim.write" + ] + }, + { + "bearer_token": [ + + ] + } + ], + "parameters": [ + { + "name": "veteranId", + "in": "path", + "required": true, + "example": "1012667145V762142", + "description": "ID of Veteran", + "schema": { + "type": "string" + } + }, + { + "name": "id", + "in": "path", + "required": true, + "example": "12e13134-7229-4e44-90ae-bcea2a4525fa", + "description": "The ID of the 21-22 submission", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Valid request response", + "content": { + "application/json": { + "example": { + "data": { + "id": "5d78a9f2-fffb-4867-a621-6ddee5bd5e58", + "type": "claimsApiPowerOfAttorneys", + "attributes": { + "status": "submitted", + "dateRequestAccepted": "2024-03-26", + "representative": { + "serviceOrganization": { + "poaCode": "074" + } + }, + "previousPoa": null + } + } + }, + "schema": { + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "type", + "attributes" + ], + "properties": { + "id": { + "type": "string", + "description": "Power of Attorney Submission UUID" + }, + "type": { + "type": "string" + }, + "attributes": { + "type": "object", + "additionalProperties": false, + "required": [ + "status", + "dateRequestAccepted", + "representative" + ], + "properties": { + "status": { + "type": "string", + "description": "Says if the power of attorney is pending, submitted, updated or errored", + "enum": [ + "pending", + "submitted", + "updated", + "errored" + ] + }, + "dateRequestAccepted": { + "type": "string", + "description": "Date request was first accepted", + "format": "date" + }, + "representative": { + "type": "object", + "additionalProperties": false, + "required": [ + "serviceOrganization" + ], + "properties": { + "serviceOrganization": { + "type": "object", + "additionalProperties": true, + "required": [ + "poaCode" + ], + "properties": { + "poa_code": { + "type": "string", + "description": "Power of Attorney Code submitted for Veteran" + } + } + } + } + }, + "previousPoa": { + "type": "string", + "nullable": true, + "description": "Current or Previous Power of Attorney Code submitted for Veteran" + } + } + } + } + } + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "example": { + "errors": [ + { + "title": "Not authorized", + "status": "401", + "detail": "Not authorized" + } + ] + }, + "schema": { + "required": [ + "errors" + ], + "properties": { + "errors": { + "type": "array", + "items": { + "additionalProperties": false, + "required": [ + "title", + "detail" + ], + "properties": { + "title": { + "type": "string", + "description": "HTTP error title" + }, + "detail": { + "type": "string", + "description": "HTTP error detail" + }, + "status": { + "type": "string", + "description": "HTTP error status code" + }, + "source": { + "type": "object", + "additionalProperties": false, + "description": "Source of error", + "properties": { + "pointer": { + "type": "string", + "description": "Pointer to source of error" + } + } + } + } + } + } + } + } + } + } + }, + "404": { + "description": "Resource not found", + "content": { + "application/json": { + "example": { + "errors": [ + { + "title": "Resource not found", + "status": "404", + "detail": "Could not find Power of Attorney with id: -1", + "source": { + "pointer": "/modules/claims_api/app/controllers/claims_api/v2/veterans/power_of_attorney/base_controller.rb:32:in `status'" } } ] diff --git a/modules/claims_api/spec/requests/v2/veterans/rswag_power_of_attorney_spec.rb b/modules/claims_api/spec/requests/v2/veterans/rswag_power_of_attorney_spec.rb index fb6eebe5285..92075a1c2fb 100644 --- a/modules/claims_api/spec/requests/v2/veterans/rswag_power_of_attorney_spec.rb +++ b/modules/claims_api/spec/requests/v2/veterans/rswag_power_of_attorney_spec.rb @@ -143,7 +143,7 @@ end end - path '/veterans/{veteranId}/2122a', production: false do + path '/veterans/{veteranId}/2122a' do post 'Appoint an individual Power of Attorney for a Veteran.' do tags 'Power of Attorney' operationId 'post2122a' @@ -312,7 +312,7 @@ end end - path '/veterans/{veteranId}/2122', production: false do + path '/veterans/{veteranId}/2122' do post 'Appoint an organization Power of Attorney for a Veteran.' do description 'Updates current Power of Attorney for Veteran.' tags 'Power of Attorney' @@ -468,7 +468,7 @@ end end - path '/veterans/{veteranId}/2122a/validate', production: false do + path '/veterans/{veteranId}/2122a/validate' do post 'Validates a 2122a form submission.' do tags 'Power of Attorney' operationId 'post2122aValidate' @@ -638,7 +638,7 @@ end end - path '/veterans/{veteranId}/2122/validate', production: false do + path '/veterans/{veteranId}/2122/validate' do post 'Validates a 2122 form submission.' do tags 'Power of Attorney' operationId 'post2122Validate' @@ -796,7 +796,7 @@ end end - path '/veterans/{veteranId}/power-of-attorney/{id}', production: false do + path '/veterans/{veteranId}/power-of-attorney/{id}' do get 'Checks status of Power of Attorney appointment form submission' do description 'Gets the Power of Attorney appointment request status (21-22/21-22a)' tags 'Power of Attorney' diff --git a/modules/claims_api/spec/support/rswag_config.rb b/modules/claims_api/spec/support/rswag_config.rb index 19474e1856e..264da3585fb 100644 --- a/modules/claims_api/spec/support/rswag_config.rb +++ b/modules/claims_api/spec/support/rswag_config.rb @@ -127,7 +127,7 @@ def config # rubocop:disable Metrics/MethodLength { name: 'Power of Attorney', description: <<~VERBIAGE - Allows authenticated and authorized users to retrieve the active power of attorney for a Veteran + Allows authenticated and authorized users to automatically establish power of attorney appointments to an organization or an individual. Organizations and individuals must be VA accredited representatives. VERBIAGE } ], From 09e2a36cbd066968d57cace3189b82c4d2b00b63 Mon Sep 17 00:00:00 2001 From: Scott <51915366+scottsdevelopment@users.noreply.github.com> Date: Tue, 26 Mar 2024 12:33:57 -0400 Subject: [PATCH 3/9] Add pension specific sentry tag (#16040) * - Add pension specific sentry tag * - Add pension specific sentry tag * - Add pension specific sentry tag * - Add pension specific sentry tag --------- Co-authored-by: Scott Gorman --- app/controllers/v0/pension_claims_controller.rb | 4 +++- .../lighthouse/pension_benefit_intake_job.rb | 3 +++ lib/pension_21p527ez/tag_sentry.rb | 13 +++++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 lib/pension_21p527ez/tag_sentry.rb diff --git a/app/controllers/v0/pension_claims_controller.rb b/app/controllers/v0/pension_claims_controller.rb index 79f039d112d..d84178c373c 100644 --- a/app/controllers/v0/pension_claims_controller.rb +++ b/app/controllers/v0/pension_claims_controller.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require 'pension_21p527ez/tag_sentry' + module V0 class PensionClaimsController < ClaimsBaseController service_tag 'pension-application' @@ -45,7 +47,7 @@ def show # Creates and validates an instance of the class, removing any copies of # the form that had been previously saved by the user. def create - PensionBurial::TagSentry.tag_sentry + Pension21p527ez::TagSentry.tag_sentry claim = claim_class.new(form: filtered_params[:form]) user_uuid = current_user&.uuid diff --git a/app/sidekiq/lighthouse/pension_benefit_intake_job.rb b/app/sidekiq/lighthouse/pension_benefit_intake_job.rb index e75b27be4f6..1decf91402d 100644 --- a/app/sidekiq/lighthouse/pension_benefit_intake_job.rb +++ b/app/sidekiq/lighthouse/pension_benefit_intake_job.rb @@ -3,6 +3,7 @@ require 'benefits_intake_service/service' require 'central_mail/datestamp_pdf' require 'simple_forms_api_submission/metadata_validator' +require 'pension_21p527ez/tag_sentry' module Lighthouse class PensionBenefitIntakeJob @@ -33,6 +34,8 @@ class PensionBenefitIntakeError < StandardError; end # @param [Integer] saved_claim_id # rubocop:disable Metrics/MethodLength def perform(saved_claim_id) + Pension21p527ez::TagSentry.tag_sentry + @saved_claim_id = saved_claim_id @claim = SavedClaim::Pension.find(saved_claim_id) raise PensionBenefitIntakeError, "Unable to find SavedClaim::Pension #{saved_claim_id}" unless @claim diff --git a/lib/pension_21p527ez/tag_sentry.rb b/lib/pension_21p527ez/tag_sentry.rb new file mode 100644 index 00000000000..d11a5a5876a --- /dev/null +++ b/lib/pension_21p527ez/tag_sentry.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Pension21p527ez + module TagSentry + module_function + + TAG_NAME = 'pension_21p527ez' + + def tag_sentry + Sentry.set_tags(feature: TAG_NAME) + end + end +end From b57cc48621fe84e2165604a0661b618cf5f8a12a Mon Sep 17 00:00:00 2001 From: Aurora <19178435+aurora-a-k-a-lightning@users.noreply.github.com> Date: Tue, 26 Mar 2024 09:43:44 -0700 Subject: [PATCH 4/9] dbex/77177: EVSS Migration - /generatePDF migration: Abstract references to EVSS /getpdf (#16080) * dbex/77177: add GeneratePdfProvider - FEATURE TOGGLE: disability_compensation_lighthouse_generate_pdf - evss - lighthouse stub(s) - unit tests * dbex/77177: add GeneratePdfProvider - fix linting and unit test * dbex/77177: add GeneratePdfProvider - fix linting and unit test * dbex/77177: add GeneratePdfProvider - added a unit test for evss service class checking --- config/features.yml | 3 ++ .../factories/api_provider_factory.rb | 27 ++++++++++- .../evss_generate_pdf_provider.rb | 22 +++++++++ .../generate_pdf/generate_pdf_provider.rb | 7 +++ .../lighthouse_generate_pdf_provider.rb | 15 ++++++ .../processor.rb | 23 ++++++++- .../factories/api_provider_factory_spec.rb | 34 +++++++++++++ .../evss_generate_pdf_provider_spec.rb | 48 +++++++++++++++++++ .../generate_pdf_provider_spec.rb | 12 +++++ .../lighthouse_generate_pdf_provider_spec.rb | 31 ++++++++++++ .../submit_spec.rb | 2 + .../job_tracker_spec.rb | 4 ++ .../generate_pdf_service_provider.rb | 10 ++++ 13 files changed, 235 insertions(+), 3 deletions(-) create mode 100644 lib/disability_compensation/providers/generate_pdf/evss_generate_pdf_provider.rb create mode 100644 lib/disability_compensation/providers/generate_pdf/generate_pdf_provider.rb create mode 100644 lib/disability_compensation/providers/generate_pdf/lighthouse_generate_pdf_provider.rb create mode 100644 spec/lib/disability_compensation/providers/generate_pdf/evss_generate_pdf_provider_spec.rb create mode 100644 spec/lib/disability_compensation/providers/generate_pdf/generate_pdf_provider_spec.rb create mode 100644 spec/lib/disability_compensation/providers/generate_pdf/lighthouse_generate_pdf_provider_spec.rb create mode 100644 spec/support/disability_compensation_form/shared_examples/generate_pdf_service_provider.rb diff --git a/config/features.yml b/config/features.yml index 1d75967a58b..068190f7557 100644 --- a/config/features.yml +++ b/config/features.yml @@ -1257,6 +1257,9 @@ features: disability_compensation_lighthouse_brd: actor_type: user description: If enabled uses the lighthouse Benefits Reference Data service + disability_compensation_lighthouse_generate_pdf: + actor_type: user + description: If enabled uses the lighthouse Benefits Claims service to generate a 526 pdf virtual_agent_fetch_jwt_token: actor_type: user description: Enable the fetching of a JWT token to access MAP environment diff --git a/lib/disability_compensation/factories/api_provider_factory.rb b/lib/disability_compensation/factories/api_provider_factory.rb index e9d7b1913b7..a8b7185306e 100644 --- a/lib/disability_compensation/factories/api_provider_factory.rb +++ b/lib/disability_compensation/factories/api_provider_factory.rb @@ -15,6 +15,9 @@ require 'disability_compensation/providers/brd/brd_provider' require 'disability_compensation/providers/brd/evss_brd_provider' require 'disability_compensation/providers/brd/lighthouse_brd_provider' +require 'disability_compensation/providers/generate_pdf/generate_pdf_provider' +require 'disability_compensation/providers/generate_pdf/evss_generate_pdf_provider' +require 'disability_compensation/providers/generate_pdf/lighthouse_generate_pdf_provider' require 'logging/third_party_transaction' class ApiProviderFactory @@ -31,7 +34,8 @@ class UndefinedFactoryTypeError < StandardError; end intent_to_file: :intent_to_file, ppiu: :ppiu, claims: :claims, - brd: :brd + brd: :brd, + generate_pdf: :generate_pdf }.freeze # Splitting the rated disabilities functionality into two use cases: @@ -47,6 +51,7 @@ class UndefinedFactoryTypeError < StandardError; end # PPIU calls out to Direct Deposit APIs in Lighthouse FEATURE_TOGGLE_PPIU_DIRECT_DEPOSIT = 'disability_compensation_lighthouse_ppiu_direct_deposit_provider' FEATURE_TOGGLE_BRD = 'disability_compensation_lighthouse_brd' + FEATURE_TOGGLE_GENERATE_PDF = 'disability_compensation_lighthouse_generate_pdf' attr_reader :type @@ -56,6 +61,7 @@ class UndefinedFactoryTypeError < StandardError; end :ppiu_service_provider, :claims_service_provider, :brd_service_provider, + :generate_pdf_service_provider, additional_class_logs: { action: 'disability compensation factory choosing API Provider' }, @@ -91,6 +97,8 @@ def call claims_service_provider when FACTORIES[:brd] brd_service_provider + when FACTORIES[:generate_pdf] + generate_pdf_service_provider else raise UndefinedFactoryTypeError end @@ -153,6 +161,23 @@ def brd_service_provider end end + def generate_pdf_service_provider + case api_provider + when API_PROVIDER[:evss] + if @options[:auth_headers].nil? || @options[:auth_headers]&.empty? + raise StandardError, 'options[:auth_headers] is required to create a generate an EVSS pdf provider' + end + + # provide options[:breakered] = false if this needs to use the non-breakered configuration + # for instance, in the backup process + EvssGeneratePdfProvider.new(@options[:auth_headers], breakered: @options[:breakered]) + when API_PROVIDER[:lighthouse] + LighthouseGeneratePdfProvider.new({}) + else + raise NotImplementedError, 'No known Generate Pdf Api Provider type provided' + end + end + def api_provider @api_provider ||= if Flipper.enabled?(@feature_toggle, @current_user) API_PROVIDER[:lighthouse] diff --git a/lib/disability_compensation/providers/generate_pdf/evss_generate_pdf_provider.rb b/lib/disability_compensation/providers/generate_pdf/evss_generate_pdf_provider.rb new file mode 100644 index 00000000000..6cd03f26212 --- /dev/null +++ b/lib/disability_compensation/providers/generate_pdf/evss_generate_pdf_provider.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require 'disability_compensation/providers/generate_pdf/generate_pdf_provider' +require 'evss/disability_compensation_form/service' +require 'evss/disability_compensation_form/non_breakered_service' + +class EvssGeneratePdfProvider + include GeneratePdfProvider + + def initialize(auth_headers, breakered: true) + # both of these services implement `get_form526` + @service = if breakered + EVSS::DisabilityCompensationForm::Service.new(auth_headers) + else + EVSS::DisabilityCompensationForm::NonBreakeredService.new(auth_headers) + end + end + + def generate_526_pdf(form_content) + @service.get_form526(form_content) + end +end diff --git a/lib/disability_compensation/providers/generate_pdf/generate_pdf_provider.rb b/lib/disability_compensation/providers/generate_pdf/generate_pdf_provider.rb new file mode 100644 index 00000000000..f914f6c9772 --- /dev/null +++ b/lib/disability_compensation/providers/generate_pdf/generate_pdf_provider.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module GeneratePdfProvider + def self.generate_526_pdf(_form_content) + raise NotImplementedError, 'Do not use base module methods. Override this method in implementation class.' + end +end diff --git a/lib/disability_compensation/providers/generate_pdf/lighthouse_generate_pdf_provider.rb b/lib/disability_compensation/providers/generate_pdf/lighthouse_generate_pdf_provider.rb new file mode 100644 index 00000000000..0384339de2d --- /dev/null +++ b/lib/disability_compensation/providers/generate_pdf/lighthouse_generate_pdf_provider.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'disability_compensation/providers/generate_pdf/generate_pdf_provider' + +class LighthouseGeneratePdfProvider + include GeneratePdfProvider + + def initialize(_auth_headers) + # TODO: Implement in Ticket# + end + + def generate_526_pdf(_form_content) + # TODO: Implement in Ticket# + end +end diff --git a/lib/sidekiq/form526_backup_submission_process/processor.rb b/lib/sidekiq/form526_backup_submission_process/processor.rb index 0394818197d..1645719f412 100644 --- a/lib/sidekiq/form526_backup_submission_process/processor.rb +++ b/lib/sidekiq/form526_backup_submission_process/processor.rb @@ -13,6 +13,7 @@ require 'pdf_fill/filler' require 'logging/third_party_transaction' require 'simple_forms_api_submission/metadata_validator' +require 'disability_compensation/factories/api_provider_factory' module Sidekiq module Form526BackupSubmissionProcess @@ -341,7 +342,10 @@ def get_form526_pdf end def get_form_from_external_api(headers, form_json) - EVSS::DisabilityCompensationForm::Service.new(headers).get_form526(form_json) + # get the "breakered" version + service = choose_provider(headers, breakered: true) + + service.generate_526_pdf(form_json) end def get_uploads @@ -423,6 +427,18 @@ def convert_doc_to_pdf(doc, klass) Common::FileHelpers.delete_file_if_exists(actual_path_to_file) if ::Rails.env.production? end end + + def choose_provider(headers, breakered: true) + ApiProviderFactory.call( + type: ApiProviderFactory::FACTORIES[:generate_pdf], + # let Flipper - the feature toggle - choose which provider + provider: nil, + # this sends the auth headers and if we want the "breakered" or "non-breakered" version + options: { auth_headers: headers, breakered: }, + current_user: OpenStruct.new({ flipper_id: submission.user_uuid }), + feature_toggle: ApiProviderFactory::FEATURE_TOGGLE_GENERATE_PDF + ) + end end class NonBreakeredProcessor < Processor @@ -444,7 +460,10 @@ def get_form526_pdf end def get_from_non_breakered_service(headers, form_json) - EVSS::DisabilityCompensationForm::NonBreakeredService.new(headers).get_form526(form_json) + # get the "non-breakered" version + service = choose_provider(headers, breakered: false) + + service.get_form526(form_json) end class NonBreakeredForm526BackgroundLoader diff --git a/spec/lib/disability_compensation/factories/api_provider_factory_spec.rb b/spec/lib/disability_compensation/factories/api_provider_factory_spec.rb index b95ce03cd19..4f5202d5c3d 100644 --- a/spec/lib/disability_compensation/factories/api_provider_factory_spec.rb +++ b/spec/lib/disability_compensation/factories/api_provider_factory_spec.rb @@ -210,4 +210,38 @@ def provider(api_provider = nil) end.to raise_error NotImplementedError end end + + context 'generate_pdf' do + def provider(api_provider = nil) + ApiProviderFactory.call( + type: ApiProviderFactory::FACTORIES[:generate_pdf], + provider: api_provider, + options: { auth_headers: }, + current_user:, + feature_toggle: ApiProviderFactory::FEATURE_TOGGLE_GENERATE_PDF + ) + end + + it 'provides an EVSS generate_pdf provider' do + expect(provider(:evss).class).to equal(EvssGeneratePdfProvider) + end + + it 'provides a Lighthouse generate_pdf provider' do + expect(provider(:lighthouse).class).to equal(LighthouseGeneratePdfProvider) + end + + it 'provides generate_pdf provider based on Flipper' do + Flipper.enable(ApiProviderFactory::FEATURE_TOGGLE_GENERATE_PDF) + expect(provider.class).to equal(LighthouseGeneratePdfProvider) + + Flipper.disable(ApiProviderFactory::FEATURE_TOGGLE_GENERATE_PDF) + expect(provider.class).to equal(EvssGeneratePdfProvider) + end + + it 'throw error if provider unknown' do + expect do + provider(:random) + end.to raise_error NotImplementedError + end + end end diff --git a/spec/lib/disability_compensation/providers/generate_pdf/evss_generate_pdf_provider_spec.rb b/spec/lib/disability_compensation/providers/generate_pdf/evss_generate_pdf_provider_spec.rb new file mode 100644 index 00000000000..030d63de9d8 --- /dev/null +++ b/spec/lib/disability_compensation/providers/generate_pdf/evss_generate_pdf_provider_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'disability_compensation/factories/api_provider_factory' +require 'disability_compensation/providers/generate_pdf/evss_generate_pdf_provider' +require 'support/disability_compensation_form/shared_examples/generate_pdf_service_provider' + +RSpec.describe EvssGeneratePdfProvider do + let(:current_user) do + create(:user) + end + + let(:auth_headers) do + EVSS::AuthHeaders.new(current_user).to_h + end + + before do + Flipper.disable(ApiProviderFactory::FEATURE_TOGGLE_GENERATE_PDF) + end + + after do + Flipper.disable(ApiProviderFactory::FEATURE_TOGGLE_GENERATE_PDF) + end + + it_behaves_like 'generate pdf service provider' + + it 'creates a breakered evss service' do + provider = EvssGeneratePdfProvider.new(auth_headers) + expect(provider.instance_variable_get(:@service).class).to equal(EVSS::DisabilityCompensationForm::Service) + + provider = EvssGeneratePdfProvider.new(auth_headers, breakered: true) + expect(provider.instance_variable_get(:@service).class).to equal(EVSS::DisabilityCompensationForm::Service) + end + + it 'creates a non-breakered evss service' do + provider = EvssGeneratePdfProvider.new(auth_headers, breakered: false) + expect(provider.instance_variable_get(:@service).class) + .to equal(EVSS::DisabilityCompensationForm::NonBreakeredService) + end + + it 'retrieves a generated 526 pdf from the EVSS API' do + VCR.use_cassette('form526_backup/200_evss_get_pdf', match_requests_on: %i[uri method]) do + provider = EvssGeneratePdfProvider.new(auth_headers) + response = provider.generate_526_pdf({}.to_json) + expect(response.body['pdf']).to eq('') + end + end +end diff --git a/spec/lib/disability_compensation/providers/generate_pdf/generate_pdf_provider_spec.rb b/spec/lib/disability_compensation/providers/generate_pdf/generate_pdf_provider_spec.rb new file mode 100644 index 00000000000..943f88d3a65 --- /dev/null +++ b/spec/lib/disability_compensation/providers/generate_pdf/generate_pdf_provider_spec.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'disability_compensation/providers/generate_pdf/generate_pdf_provider' + +RSpec.describe GeneratePdfProvider do + it 'always raises an error on the BRDProvider base module' do + expect do + subject.generate_526_pdf({}) + end.to raise_error NotImplementedError + end +end diff --git a/spec/lib/disability_compensation/providers/generate_pdf/lighthouse_generate_pdf_provider_spec.rb b/spec/lib/disability_compensation/providers/generate_pdf/lighthouse_generate_pdf_provider_spec.rb new file mode 100644 index 00000000000..808fb69a552 --- /dev/null +++ b/spec/lib/disability_compensation/providers/generate_pdf/lighthouse_generate_pdf_provider_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'disability_compensation/factories/api_provider_factory' +require 'disability_compensation/providers/generate_pdf/lighthouse_generate_pdf_provider' +require 'support/disability_compensation_form/shared_examples/generate_pdf_service_provider' +require 'lighthouse/service_exception' + +RSpec.describe LighthouseGeneratePdfProvider do + let(:auth_headers) { {} } + + before do + @provider = LighthouseGeneratePdfProvider.new(auth_headers) + Flipper.enable(ApiProviderFactory::FEATURE_TOGGLE_GENERATE_PDF) + end + + after do + Flipper.disable(ApiProviderFactory::FEATURE_TOGGLE_GENERATE_PDF) + end + + it_behaves_like 'generate pdf service provider' + + # TODO: Implement in Ticket# + # it 'retrieves a generated 526 pdf from the Lighthouse API' do + # VCR.use_cassette('lighthouse/benefits_claims/generate_pdf/200_response') do + # + # response = @provider.generate_526_pdf + # expect(response).to eq(nil) + # end + # end +end diff --git a/spec/lib/sidekiq/form526_backup_submission_process/submit_spec.rb b/spec/lib/sidekiq/form526_backup_submission_process/submit_spec.rb index 69ff18ad1ff..0ec092ed2f5 100644 --- a/spec/lib/sidekiq/form526_backup_submission_process/submit_spec.rb +++ b/spec/lib/sidekiq/form526_backup_submission_process/submit_spec.rb @@ -4,12 +4,14 @@ require 'evss/disability_compensation_auth_headers' # required to build a Form526Submission require 'sidekiq/form526_backup_submission_process/submit' +require 'disability_compensation/factories/api_provider_factory' RSpec.describe Sidekiq::Form526BackupSubmissionProcess::Submit, type: :job do subject { described_class } before do Sidekiq::Job.clear_all + Flipper.disable(ApiProviderFactory::FEATURE_TOGGLE_GENERATE_PDF) end let(:user) { FactoryBot.create(:user, :loa3) } diff --git a/spec/lib/sidekiq/form526_job_status_tracker/job_tracker_spec.rb b/spec/lib/sidekiq/form526_job_status_tracker/job_tracker_spec.rb index a8c84527c9b..8ed993a919b 100644 --- a/spec/lib/sidekiq/form526_job_status_tracker/job_tracker_spec.rb +++ b/spec/lib/sidekiq/form526_job_status_tracker/job_tracker_spec.rb @@ -11,6 +11,10 @@ end end + before do + Flipper.disable(ApiProviderFactory::FEATURE_TOGGLE_GENERATE_PDF) + end + context 'with an exhausted callback message' do let!(:form526_submission) { create :form526_submission } let!(:form526_job_status) do diff --git a/spec/support/disability_compensation_form/shared_examples/generate_pdf_service_provider.rb b/spec/support/disability_compensation_form/shared_examples/generate_pdf_service_provider.rb new file mode 100644 index 00000000000..76fdc0fbf20 --- /dev/null +++ b/spec/support/disability_compensation_form/shared_examples/generate_pdf_service_provider.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require 'rails_helper' + +shared_examples 'generate pdf service provider' do + # this is used to instantiate any Claim Service with a current_user + subject { described_class.new(auth_headers) } + + it { is_expected.to respond_to(:generate_526_pdf) } +end From 8253e76a36952dc9d656bcd52c49006147ddae7c Mon Sep 17 00:00:00 2001 From: Richard Davis Date: Tue, 26 Mar 2024 12:53:57 -0400 Subject: [PATCH 5/9] [Bugfix] Personal health care contacts: Selects only valid contact types (#16062) * Selects only valid contact types --- .../profile/v3/health_benefit_bio_response.rb | 44 ++++++++----- .../v3/health_benefit_bio_response_spec.rb | 62 +++++++++++++++++++ .../lib/va_profile/profile/v3/service_spec.rb | 56 +++++++++++++---- .../profile/v3/health_benefit_bio_500.yml | 61 ++++++++++++++++++ 4 files changed, 196 insertions(+), 27 deletions(-) create mode 100644 spec/lib/va_profile/profile/v3/health_benefit_bio_response_spec.rb create mode 100644 spec/support/vcr_cassettes/va_profile/profile/v3/health_benefit_bio_500.yml diff --git a/lib/va_profile/profile/v3/health_benefit_bio_response.rb b/lib/va_profile/profile/v3/health_benefit_bio_response.rb index ffea96f8731..1dde3fb9f89 100644 --- a/lib/va_profile/profile/v3/health_benefit_bio_response.rb +++ b/lib/va_profile/profile/v3/health_benefit_bio_response.rb @@ -4,23 +4,39 @@ require 'va_profile/models/associated_person' require 'va_profile/models/message' -module VAProfile::Profile::V3 - class HealthBenefitBioResponse < VAProfile::Response - attr_reader :body +module VAProfile + module Profile + module V3 + class HealthBenefitBioResponse < VAProfile::Response + attr_reader :body - attribute :contacts, Array[VAProfile::Models::AssociatedPerson] - attribute :messages, Array[VAProfile::Models::Message] + attribute :contacts, Array[VAProfile::Models::AssociatedPerson] + attribute :messages, Array[VAProfile::Models::Message] - def initialize(response) - @body = response.body - contacts = body.dig('profile', 'health_benefit', 'associated_persons') - &.sort_by { |p| VAProfile::Models::AssociatedPerson::CONTACT_TYPES.index(p['contact_type']) } - messages = body['messages'] - super(response.status, { contacts:, messages: }) - end + def initialize(response) + @body = response.body + contacts = body.dig('profile', 'health_benefit', 'associated_persons') + &.select { |p| valid_contact_types.include?(p['contact_type']) } + &.sort_by { |p| valid_contact_types.index(p['contact_type']) } + messages = body['messages'] + super(response.status, { contacts:, messages: }) + end + + def metadata + { status:, messages: } + end + + private - def metadata - { status:, messages: } + def valid_contact_types + [ + VAProfile::Models::AssociatedPerson::EMERGENCY_CONTACT, + VAProfile::Models::AssociatedPerson::OTHER_EMERGENCY_CONTACT, + VAProfile::Models::AssociatedPerson::PRIMARY_NEXT_OF_KIN, + VAProfile::Models::AssociatedPerson::OTHER_NEXT_OF_KIN + ] + end + end end end end diff --git a/spec/lib/va_profile/profile/v3/health_benefit_bio_response_spec.rb b/spec/lib/va_profile/profile/v3/health_benefit_bio_response_spec.rb new file mode 100644 index 00000000000..70f51f3ed61 --- /dev/null +++ b/spec/lib/va_profile/profile/v3/health_benefit_bio_response_spec.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'va_profile/profile/v3/health_benefit_bio_response' + +describe VAProfile::Profile::V3::HealthBenefitBioResponse do + subject { described_class.new(response) } + + let(:response) do + double('Faraday::Response', + status: 200, + body: { + 'profile' => { + 'health_benefit' => { + 'associated_persons' => [{ + 'contact_type' => contact_type + }] + } + } + }) + end + + describe 'Emergency contact' do + let(:contact_type) { 'Emergency Contact' } + + it 'includes contact' do + expect(subject.contacts).not_to be_empty + end + end + + describe 'Other emergency contact' do + let(:contact_type) { 'Other emergency contact' } + + it 'includes contact' do + expect(subject.contacts).not_to be_empty + end + end + + describe 'Primary Next of Kin' do + let(:contact_type) { 'Primary Next of Kin' } + + it 'includes contact' do + expect(subject.contacts).not_to be_empty + end + end + + describe 'Other Next of Kin' do + let(:contact_type) { 'Other Next of Kin' } + + it 'includes contact' do + expect(subject.contacts).not_to be_empty + end + end + + describe 'Invalid contact type' do + let(:contact_type) { 'Invalid type' } + + it 'does not include contact' do + expect(subject.contacts).to be_empty + end + end +end diff --git a/spec/lib/va_profile/profile/v3/service_spec.rb b/spec/lib/va_profile/profile/v3/service_spec.rb index ddcae4f7b05..da5ec7b9b89 100644 --- a/spec/lib/va_profile/profile/v3/service_spec.rb +++ b/spec/lib/va_profile/profile/v3/service_spec.rb @@ -55,30 +55,60 @@ describe '#get_health_benefit_bio' do let(:user) { build(:user, :loa3, idme_uuid:) } + around do |ex| + VCR.use_cassette(cassette) { ex.run } + end + context '200 response' do let(:idme_uuid) { 'dd681e7d6dea41ad8b80f8d39284ef29' } + let(:cassette) { 'va_profile/profile/v3/health_benefit_bio_200' } it 'returns the contacts (aka associated_persons) for a user, sorted' do - VCR.use_cassette('va_profile/profile/v3/health_benefit_bio_200') do - response = subject.get_health_benefit_bio - expect(response.status).to eq(200) - expect(response.contacts.size).to eq(4) - types = response.contacts.map(&:contact_type) - expect(types).to match_array(VAProfile::Models::AssociatedPerson::CONTACT_TYPES) - end + response = subject.get_health_benefit_bio + expect(response.status).to eq(200) + expect(response.contacts.size).to eq(4) + types = response.contacts.map(&:contact_type) + valid_contact_types = [ + VAProfile::Models::AssociatedPerson::EMERGENCY_CONTACT, + VAProfile::Models::AssociatedPerson::OTHER_EMERGENCY_CONTACT, + VAProfile::Models::AssociatedPerson::PRIMARY_NEXT_OF_KIN, + VAProfile::Models::AssociatedPerson::OTHER_NEXT_OF_KIN + ] + expect(types).to match_array(valid_contact_types) end end context '404 response' do let(:idme_uuid) { '88f572d4-91af-46ef-a393-cba6c351e252' } + let(:cassette) { 'va_profile/profile/v3/health_benefit_bio_404' } + + it 'includes messages received from the api' do + response = subject.get_health_benefit_bio + expect(response.status).to eq(404) + expect(response.contacts.size).to eq(0) + expect(response.messages.size).to eq(1) + end + end + + context '500 response' do + let(:idme_uuid) { '88f572d4-91af-46ef-a393-cba6c351e252' } + let(:cassette) { 'va_profile/profile/v3/health_benefit_bio_500' } it 'includes messages recieved from the api' do - VCR.use_cassette('va_profile/profile/v3/health_benefit_bio_404') do - response = subject.get_health_benefit_bio - expect(response.status).to eq(404) - expect(response.contacts.size).to eq(0) - expect(response.messages.size).to eq(1) - end + response = subject.get_health_benefit_bio + expect(response.status).to eq(500) + expect(response.contacts.size).to eq(0) + expect(response.messages.size).to eq(1) + end + end + + context 'api timeout' do + let(:idme_uuid) { '88f572d4-91af-46ef-a393-cba6c351e252' } + let(:cassette) { 'va_profile/profile/v3/health_benefit_bio_500' } + + it 'raises an error' do + allow_any_instance_of(Faraday::Connection).to receive(:post).and_raise(Faraday::TimeoutError) + expect { subject.get_health_benefit_bio }.to raise_error(Common::Exceptions::GatewayTimeout) end end end diff --git a/spec/support/vcr_cassettes/va_profile/profile/v3/health_benefit_bio_500.yml b/spec/support/vcr_cassettes/va_profile/profile/v3/health_benefit_bio_500.yml new file mode 100644 index 00000000000..66944f355f2 --- /dev/null +++ b/spec/support/vcr_cassettes/va_profile/profile/v3/health_benefit_bio_500.yml @@ -0,0 +1,61 @@ +--- +http_interactions: +- request: + method: post + uri: https://int.vet360.va.gov/profile-service/profile/v3/2.16.840.1.113883.4.349/88f572d4-91af-46ef-a393-cba6c351e252%5EPN%5E200VIDM%5EUSDVA + body: + encoding: UTF-8 + string: '{"bios":[{"bioPath":"healthBenefit"}]}' + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - Vets.gov Agent + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 500 + message: Internal Server Error + headers: + X-Oneagent-Js-Injection: + - 'true' + Server-Timing: + - dtRpid;desc="-552585048", dtSInfo;desc="0" + - dtRpid;desc="667499283", dtSInfo;desc="0" + Vaprofiletxauditid: + - ab45e9d3-6491-4062-8384-40d23135d252 + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - '0' + Cache-Control: + - no-cache, no-store, max-age=0, must-revalidate + Pragma: + - no-cache + Expires: + - '0' + X-Frame-Options: + - DENY + Content-Security-Policy: + - 'default-src ''self'' ''unsafe-eval'' ''unsafe-inline'' data: filesystem: + about: blob: ws: wss:' + Date: + - Mon, 05 Feb 2024 22:13:44 GMT + Referrer-Policy: + - no-referrer + Content-Type: + - application/json + Content-Length: + - '184' + Strict-Transport-Security: + - max-age=16000000; includeSubDomains; preload; + body: + encoding: UTF-8 + string: '{"messages":[{"code":"MVI203","key":"MviResponseError","text":"MVI returned + acknowledgement error code AE with error detail: More Than One Active Correlation + Exists","severity":"FATAL"}]}' + recorded_at: Mon, 05 Feb 2024 22:13:44 GMT +recorded_with: VCR 6.2.0 From 137d71f97326e97ff22d6c3bd14fddc733a10b9f Mon Sep 17 00:00:00 2001 From: Joshua Backfield <114932557+jbackfieldVA@users.noreply.github.com> Date: Tue, 26 Mar 2024 13:48:24 -0400 Subject: [PATCH 6/9] Updating version of REDIS to 6.2 in the docker compose files. (#16090) --- docker-compose-deps.yml | 2 +- docker-compose.review.yml | 2 +- docker-compose.yml | 2 +- docs/setup/native.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docker-compose-deps.yml b/docker-compose-deps.yml index cbf024558b2..b1ab99a4d31 100644 --- a/docker-compose-deps.yml +++ b/docker-compose-deps.yml @@ -1,7 +1,7 @@ version: '3.4' services: redis: - image: redis:5.0-alpine + image: redis:6.2-alpine ports: - "63790:6379" postgres: diff --git a/docker-compose.review.yml b/docker-compose.review.yml index 0c26ee35031..43769083ab7 100644 --- a/docker-compose.review.yml +++ b/docker-compose.review.yml @@ -1,7 +1,7 @@ version: '3.4' services: redis: - image: redis:5.0-alpine + image: redis:6.2-alpine restart: unless-stopped postgres: image: mdillon/postgis:11-alpine diff --git a/docker-compose.yml b/docker-compose.yml index 0a3196fbb65..bf46aa633b9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ version: '3.4' services: redis: - image: redis:5.0-alpine + image: redis:6.2-alpine ports: - "63790:6379" postgres: diff --git a/docs/setup/native.md b/docs/setup/native.md index 8ec15de8acc..a01b152f3a4 100644 --- a/docs/setup/native.md +++ b/docs/setup/native.md @@ -4,7 +4,7 @@ Vets API requires: - Ruby 3.2.3 - PostgreSQL 15.x (including PostGIS 3) -- Redis 5.0.x +- Redis 6.2.x The most up-to-date versions of each key dependency will be specified in the `docker-compose.yml` [file](https://github.com/department-of-veterans-affairs/vets-api/blob/master/docker-compose.yml) and the `Dockerfile`. From ab0a2695ab02355fab5c889ec3bc115ba8cf195f Mon Sep 17 00:00:00 2001 From: Athif Wulandana Date: Tue, 26 Mar 2024 13:47:18 -0500 Subject: [PATCH 7/9] Authorized Ping Endpoint for Travel Pay API (#16089) * Add authorized_ping endpoint to BTSSS API * Adds specs for authorized ping endpoint, cleanup and bug fixes * Fixes for rubocop * Adds line comment --------- Co-authored-by: Athif Wulandana --- .../travel_pay/pings_controller.rb | 11 ++++++ .../app/services/travel_pay/client.rb | 16 ++++++++ modules/travel_pay/config/routes.rb | 1 + .../spec/controllers/pings_controller_spec.rb | 38 +++++++++++++++++-- .../travel_pay/spec/services/client_spec.rb | 17 +++++++++ 5 files changed, 80 insertions(+), 3 deletions(-) diff --git a/modules/travel_pay/app/controllers/travel_pay/pings_controller.rb b/modules/travel_pay/app/controllers/travel_pay/pings_controller.rb index 5f5e9332e4e..8529b72d842 100644 --- a/modules/travel_pay/app/controllers/travel_pay/pings_controller.rb +++ b/modules/travel_pay/app/controllers/travel_pay/pings_controller.rb @@ -10,6 +10,17 @@ def ping render json: { data: "Received ping from upstream server with status #{btsss_ping_response.status}." } end + def authorized_ping + vagov_token = request.headers['Authorization'].split[1] + veis_token = client.request_veis_token + btsss_token = client.request_btsss_token(veis_token, vagov_token) + + btsss_authorized_ping_response = client.authorized_ping(veis_token, btsss_token) + render json: { + data: "Received authorized ping from upstream server with status #{btsss_authorized_ping_response.status}." + } + end + def client TravelPay::Client.new end diff --git a/modules/travel_pay/app/services/travel_pay/client.rb b/modules/travel_pay/app/services/travel_pay/client.rb index 40e2f461a28..79424725b3a 100644 --- a/modules/travel_pay/app/services/travel_pay/client.rb +++ b/modules/travel_pay/app/services/travel_pay/client.rb @@ -52,6 +52,22 @@ def ping(veis_token) end end + ## + # HTTP GET call to the BTSSS 'authorized-ping' endpoint to test liveness + # + # @return [Faraday::Response] + # + def authorized_ping(veis_token, btsss_token) + btsss_url = Settings.travel_pay.base_url + api_key = Settings.travel_pay.subscription_key + + connection(server_url: btsss_url).get('api/v1/Sample/authorized-ping') do |req| + req.headers['Authorization'] = "Bearer #{veis_token}" + req.headers['BTSSS-Access-Token'] = btsss_token + req.headers['Ocp-Apim-Subscription-Key'] = api_key + end + end + ## # HTTP GET call to the BTSSS 'claims' endpoint # API responds with travel pay claims including status diff --git a/modules/travel_pay/config/routes.rb b/modules/travel_pay/config/routes.rb index a0751ec089e..0f62badc5ae 100644 --- a/modules/travel_pay/config/routes.rb +++ b/modules/travel_pay/config/routes.rb @@ -2,5 +2,6 @@ TravelPay::Engine.routes.draw do get '/pings/ping', to: 'pings#ping' + get '/pings/authorized_ping', to: 'pings#authorized_ping' resources :claims end diff --git a/modules/travel_pay/spec/controllers/pings_controller_spec.rb b/modules/travel_pay/spec/controllers/pings_controller_spec.rb index 33dd8a9a537..750a96600bf 100644 --- a/modules/travel_pay/spec/controllers/pings_controller_spec.rb +++ b/modules/travel_pay/spec/controllers/pings_controller_spec.rb @@ -8,9 +8,7 @@ before do allow(TravelPay::Client).to receive(:new).and_return(client) - veis_response = double - allow(veis_response).to receive(:body).and_return('sample_token') - allow(client).to receive(:request_veis_token).and_return(veis_response) + allow(client).to receive(:request_veis_token).and_return('sample_token') btsss_ping_response = double allow(btsss_ping_response).to receive(:status).and_return(200) @@ -45,4 +43,38 @@ end end end + + describe '#authorized_ping' do + before do + btsss_authorized_ping_response = double + allow(btsss_authorized_ping_response).to receive(:status).and_return(200) + allow(client) + .to receive(:request_btsss_token) + .and_return('sample_btsss_token') + allow(client) + .to receive(:authorized_ping) + .with('sample_token', 'sample_btsss_token') + .and_return(btsss_authorized_ping_response) + end + + context 'the feature switch is enabled' do + before do + Flipper.enable :travel_pay_power_switch + end + + it 'requests a token and sends a ping to BTSSS' do + expect(client).to receive(:authorized_ping) + get '/travel_pay/pings/authorized_ping', headers: { 'Authorization' => 'Bearer vagov_token' } + expect(response.body).to include('authorized ping') + end + end + + context 'the feature switch is disabled' do + it 'raises the proper error' do + get '/travel_pay/pings/authorized_ping', headers: { 'Authorization' => 'Bearer vagov_token' } + expect(response).to have_http_status(:service_unavailable) + expect(response.body).to include('This feature has been temporarily disabled') + end + end + end end diff --git a/modules/travel_pay/spec/services/client_spec.rb b/modules/travel_pay/spec/services/client_spec.rb index c88cb9d230e..2fe2c43d5ed 100644 --- a/modules/travel_pay/spec/services/client_spec.rb +++ b/modules/travel_pay/spec/services/client_spec.rb @@ -77,6 +77,23 @@ actual_claim_ids = claims.pluck(:id) expect(actual_claim_ids).to eq(expected_ordered_ids) + end + end + + context 'authorized_ping' do + it 'receives response from authorized-ping endpoint' do + allow(Settings.travel_pay.veis).to receive(:auth_url).and_return('sample_url') + allow(Settings.travel_pay.veis).to receive(:tenant_id).and_return('sample_id') + @stubs.get('/api/v1/Sample/authorized-ping') do + [ + 200, + { 'Content-Type': 'application/json' } + ] + end + client = TravelPay::Client.new + response = client.authorized_ping('veis_token', 'btsss_token') + + expect(response).to be_success @stubs.verify_stubbed_calls end end From 2b7d0e72cd77121e29a97674672826ee489fdcf0 Mon Sep 17 00:00:00 2001 From: Adam Antonioli <132934480+adam-antonioli@users.noreply.github.com> Date: Tue, 26 Mar 2024 15:01:15 -0400 Subject: [PATCH 8/9] MBMS-61276 change UUID size and position on pre need form 40-10007 pdf (#16074) --- .../app/services/simple_forms_api/pdf_stamper.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/simple_forms_api/app/services/simple_forms_api/pdf_stamper.rb b/modules/simple_forms_api/app/services/simple_forms_api/pdf_stamper.rb index a0f8d61bcb7..2d4793d0cb2 100644 --- a/modules/simple_forms_api/app/services/simple_forms_api/pdf_stamper.rb +++ b/modules/simple_forms_api/app/services/simple_forms_api/pdf_stamper.rb @@ -168,12 +168,12 @@ def self.stamp2010207(stamped_template_path, form) def self.stamp4010007_uuid(uuid) uuid = "UUID: #{uuid}" stamped_template_path = 'tmp/vba_40_10007-tmp.pdf' - desired_stamps = [[410, 20]] + desired_stamps = [[390, 18]] page_configuration = [ { type: :text, position: desired_stamps[0] } ] - verified_multistamp(stamped_template_path, uuid, page_configuration, 7) + verified_multistamp(stamped_template_path, uuid, page_configuration, 9) end def self.multistamp(stamped_template_path, signature_text, page_configuration, font_size = 16) From 6675535b5cb3a44e0301c2ef73b37e81f476def6 Mon Sep 17 00:00:00 2001 From: Alex Morgun <87077843+oleksii-morgun@users.noreply.github.com> Date: Tue, 26 Mar 2024 16:40:02 -0400 Subject: [PATCH 9/9] [#MHV-54372] remove 556 transition feature flags (#15990) Co-authored-by: Alex Morgun <87077843+oleksii-morgun-va@users.noreply.github.com> --- config/features.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/config/features.yml b/config/features.yml index 068190f7557..d9e65f1cdb8 100644 --- a/config/features.yml +++ b/config/features.yml @@ -73,12 +73,6 @@ features: actor_type: user description: Enables access to the 10-10EZR application in prod for the purposes of conducting user reasearch enable_in_development: true - cerner_transition_556_t30: - actor_type: user - description: This will control the content that will be in effect 30 days prior to transition. - cerner_transition_556_t5: - actor_type: user - description: This will control the content that will be in effect 5 days prior to transition. cerner_override_653: actor_type: user description: This will show the Cerner facility 653 as `isCerner`.