diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index dd618a068a2..8a2edf8c75f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1502,6 +1502,7 @@ spec/lib/pdf_info/metadata_spec.rb @department-of-veterans-affairs/va-api-engine spec/lib/pdf_utilities @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group spec/lib/pension_burial @department-of-veterans-affairs/mbs-core-team @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group spec/lib/pension21p527ez/pension_military_information_spec.rb @department-of-veterans-affairs/pension-and-burials @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group +spec/lib/periodic_jobs_spec.rb @department-of-veterans-affairs/govcio-vfep-codereviewers @department-of-veterans-affairs/backend-review-group spec/lib/preneeds @department-of-veterans-affairs/mbs-core-team @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group spec/lib/post911_sob @department-of-veterans-affairs/govcio-vfep-codereviewers @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group spec/lib/rx @department-of-veterans-affairs/vfs-mhv-medications diff --git a/Gemfile b/Gemfile index 75ac528b3d7..c55e94ca519 100644 --- a/Gemfile +++ b/Gemfile @@ -207,6 +207,7 @@ group :development, :test do gem 'byebug', platforms: :ruby # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'danger' gem 'database_cleaner' + gem 'debug' gem 'factory_bot_rails' gem 'faker' # CAUTION: faraday_curl may not provide all headers used in the actual faraday request. Be cautious if using this to diff --git a/Gemfile.lock b/Gemfile.lock index 21774f8aa0c..4c27f438221 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -313,7 +313,7 @@ GEM coercible (1.0.0) descendants_tracker (~> 0.0.1) colored2 (3.1.2) - combine_pdf (1.0.26) + combine_pdf (1.0.29) matrix ruby-rc4 (>= 0.1.5) committee (5.4.0) @@ -375,6 +375,9 @@ GEM libddwaf (~> 1.14.0.0.0) msgpack debase-ruby_core_source (3.3.1) + debug (1.9.2) + irb (~> 1.10) + reline (>= 0.3.8) declarative (0.0.20) deep_merge (1.2.2) descendants_tracker (0.0.4) @@ -1181,6 +1184,7 @@ DEPENDENCIES date_validator ddtrace debts_api! + debug decision_reviews! dhp_connected_devices! dogstatsd-ruby (= 5.6.3) diff --git a/app/controllers/v0/disability_compensation_forms_controller.rb b/app/controllers/v0/disability_compensation_forms_controller.rb index 05715282ffb..eee5dc0b064 100644 --- a/app/controllers/v0/disability_compensation_forms_controller.rb +++ b/app/controllers/v0/disability_compensation_forms_controller.rb @@ -35,9 +35,10 @@ def separation_locations :all_users, :get_separation_locations ) do + provider = Flipper.enabled?(:disability_compensation_staging_lighthouse_brd) ? :lighthouse_staging : nil api_provider = ApiProviderFactory.call( type: ApiProviderFactory::FACTORIES[:brd], - provider: nil, + provider:, options: {}, current_user: @current_user, feature_toggle: ApiProviderFactory::FEATURE_TOGGLE_BRD diff --git a/config/features.yml b/config/features.yml index c8cb0cb29c6..b221c1aeefb 100644 --- a/config/features.yml +++ b/config/features.yml @@ -87,6 +87,9 @@ features: caregiver_retry_form_validation: actor_type: user description: Enables 1010CG to retry schema validation + disability_compensation_staging_lighthouse_brd: + actor_type: user + description: Switches to Lighthouse Staging BRD Service. NEVER ENABLE IN PRODUCTION. hca_browser_monitoring_enabled: actor_type: user description: Enables browser monitoring for the health care application. diff --git a/db/migrate/20241213171608_add_path_to_banners.rb b/db/migrate/20241213171608_add_path_to_banners.rb new file mode 100644 index 00000000000..88663e24b92 --- /dev/null +++ b/db/migrate/20241213171608_add_path_to_banners.rb @@ -0,0 +1,5 @@ +class AddPathToBanners < ActiveRecord::Migration[7.2] + def change + add_column :banners, :path, :string + end +end diff --git a/db/migrate/20241213173113_add_index_to_banners_path.rb b/db/migrate/20241213173113_add_index_to_banners_path.rb new file mode 100644 index 00000000000..601177f4ce8 --- /dev/null +++ b/db/migrate/20241213173113_add_index_to_banners_path.rb @@ -0,0 +1,7 @@ +class AddIndexToBannersPath < ActiveRecord::Migration[7.2] + disable_ddl_transaction! + + def change + add_index :banners, :path, algorithm: :concurrently + end +end diff --git a/db/schema.rb b/db/schema.rb index f099553cb06..16899188a79 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.2].define(version: 2024_12_12_101623) do +ActiveRecord::Schema[7.2].define(version: 2024_12_13_173113) do # These are extensions that must be enabled in order to support this database enable_extension "btree_gin" enable_extension "fuzzystrmatch" @@ -310,7 +310,9 @@ t.boolean "limit_subpage_inheritance" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.string "path" t.index ["entity_id"], name: "index_banners_on_entity_id" + t.index ["path"], name: "index_banners_on_path" end create_table "base_facilities", id: false, force: :cascade do |t| diff --git a/lib/disability_compensation/factories/api_provider_factory.rb b/lib/disability_compensation/factories/api_provider_factory.rb index a5da3ee093c..d100bac04fd 100644 --- a/lib/disability_compensation/factories/api_provider_factory.rb +++ b/lib/disability_compensation/factories/api_provider_factory.rb @@ -15,6 +15,7 @@ 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/brd/lighthouse_staging_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' @@ -28,7 +29,8 @@ class UndefinedFactoryTypeError < StandardError; end API_PROVIDER = { evss: :evss, - lighthouse: :lighthouse + lighthouse: :lighthouse, + lighthouse_staging: :lighthouse_staging }.freeze FACTORIES = { @@ -166,6 +168,8 @@ def brd_service_provider EvssBRDProvider.new(@current_user) when API_PROVIDER[:lighthouse] LighthouseBRDProvider.new(@current_user) + when API_PROVIDER[:lighthouse_staging] + LighthouseStagingBRDProvider.new(@current_user) else raise NotImplementedError, 'No known BRD Api Provider type provided' end diff --git a/lib/disability_compensation/providers/brd/lighthouse_staging_brd_provider.rb b/lib/disability_compensation/providers/brd/lighthouse_staging_brd_provider.rb new file mode 100644 index 00000000000..a8fb0139e89 --- /dev/null +++ b/lib/disability_compensation/providers/brd/lighthouse_staging_brd_provider.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'disability_compensation/providers/brd/lighthouse_brd_provider' +require 'lighthouse/benefits_reference_data_staging/service' + +class LighthouseStagingBRDProvider < LighthouseBRDProvider + def initialize(_current_user) + super + @service = BenefitsReferenceData::Staging::Service.new + end +end diff --git a/lib/lighthouse/benefits_reference_data_staging/configuration.rb b/lib/lighthouse/benefits_reference_data_staging/configuration.rb new file mode 100644 index 00000000000..6696a31879a --- /dev/null +++ b/lib/lighthouse/benefits_reference_data_staging/configuration.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +require 'common/client/configuration/rest' +require 'faraday/multipart' + +module BenefitsReferenceData + ## + # HTTP client configuration for the {BenefitsReferenceData::Service}, + # sets the base path, the base request headers, and a service name for breakers and metrics. + + module Staging + class Configuration < Common::Client::Configuration::REST + self.read_timeout = Settings.lighthouse.benefits_reference_data.timeout || 20 + + ## + # @return [String] Base path for benefits_reference_data URLs. + # + def base_path + settings = Settings.lighthouse.benefits_reference_data + url = settings.staging_url + path = settings.path + version = settings.version + safe_slash_merge(url, path, version) + end + + ## + # @return [String] Service name to use in breakers and metrics. + # + def service_name + 'BenefitsReferenceDataStaging' + end + + ## + # @return [Hash] The basic headers required for any benefits_reference_data API call. + # + def self.base_request_headers + key = Settings.lighthouse.staging_api_key + message = "No api_key set for LH benefits_reference_data_staging. Please set 'lighthouse.staging_api_key'" + raise message if key.nil? + + super.merge('apiKey' => key) + end + + ## + # Creates the a connection with parsing json and adding breakers functionality. + # + # @return [Faraday::Connection] a Faraday connection instance. + # + def connection + @conn ||= Faraday.new(base_path, headers: base_request_headers, request: request_options) do |faraday| + faraday.use :breakers + faraday.use Faraday::Response::RaiseError + + faraday.request :multipart + faraday.request :json + faraday.response :json + faraday.adapter Faraday.default_adapter + end + end + + private + + def safe_slash_merge(*url_segments) + url_segments.map { |segment| segment.sub(%r{^/}, '').chomp('/') }.join('/') + end + end + end +end diff --git a/lib/lighthouse/benefits_reference_data_staging/service.rb b/lib/lighthouse/benefits_reference_data_staging/service.rb new file mode 100644 index 00000000000..ada49d3bc76 --- /dev/null +++ b/lib/lighthouse/benefits_reference_data_staging/service.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require 'common/client/base' +require 'common/client/concerns/monitoring' +require 'common/client/errors' +require 'common/exceptions/forbidden' +require 'common/exceptions/schema_validation_errors' +require 'lighthouse/benefits_reference_data_staging/configuration' +require 'lighthouse/benefits_reference_data/service_exception' + +module BenefitsReferenceData + ## + # Proxy Service for the Lighthouse Benefits Reference Data API. + + module Staging + class Service < Common::Client::Base + include SentryLogging + include Common::Client::Concerns::Monitoring + + configuration BenefitsReferenceData::Staging::Configuration + + # ap @configuration.base_request_headers; exit + + STATSD_KEY_PREFIX = 'api.benefits_reference_data_staging' + + ## + # Hit a Benefits Reference Data End-point + # + # @path end-point [string|symbol] a string or symbol of the end-point you wish to hit. + # @params params hash [Hash] a hash of key-value pairs of parameters + # + # @return [Faraday::Response] + # + def get_data(path:, params: {}) + headers = config.base_request_headers + begin + response = perform :get, path, params, headers + rescue => e + raise BenefitsReferenceData::ServiceException.new(e), 'Lighthouse Error' + end + response + end + end + end +end diff --git a/lib/periodic_jobs.rb b/lib/periodic_jobs.rb index 7f76e0ee6f3..e1e8273f2c0 100644 --- a/lib/periodic_jobs.rb +++ b/lib/periodic_jobs.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require 'holidays' + # @see https://crontab.guru/ # @see https://en.wikipedia.org/wiki/Cron PERIODIC_JOBS = lambda { |mgr| # rubocop:disable Metrics/BlockLength @@ -249,10 +251,8 @@ # Daily 0000 hrs job for Vye: performs ingress of state from BDN & TIMS. mgr.register('15 00 * * 1-5', 'Vye::MidnightRun::IngressBdn') mgr.register('45 03 * * 1-5', 'Vye::MidnightRun::IngressTims') - # Daily 0600 hrs job for Vye: activates ingressed state, and egresses the changes for the day. mgr.register('45 05 * * 1-5', 'Vye::DawnDash') - - # Daily job for Vye: clears deactivated BDNs every evening. + # Daily 1900 job for Vye: clears deactivated BDNs every evening. mgr.register('00 19 * * 1-5', 'Vye::SundownSweep') } 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 1d4088a4e78..4304613d734 100644 --- a/modules/claims_api/app/swagger/claims_api/description/v2.md +++ b/modules/claims_api/app/swagger/claims_api/description/v2.md @@ -28,7 +28,7 @@ End-to-end claims tracking provides the status of claims as they move through th ### Claim statuses -After you submit a disability compensation claim with the `POST /veterans/{veteranId}/526` synchronous endpoint, it is then established in Veterans Benefits Management System (VBMS). A `202` response means that the claim was successfully submitted by the API. However, it does not mean VA has received the required 526EZ PDF. +After you submit a disability compensation claim with the `POST /veterans/{veteranId}/526/synchronous` endpoint, it is then established in Veterans Benefits Management System (VBMS). A `202` response means that the claim was successfully submitted by the API. However, it does not mean VA has received the required 526EZ PDF. To confirm the status of your submission, use the `GET /veterans/{veteranId}/claims/{id}` endpoint and the ID returned with your submission response. Statuses are: 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 83b3b36d31e..6c7e7cf2b5f 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 organization of individual with power of attorney for a claimant.\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## Appointing an accredited representative for dependents\n\nDependents of Veterans, such as spouses, children (biological and step), and parents (biological and foster) may be eligible for VA benefits and can request representation by an accredited representative.\n\nTo file claims through an accredited representative, dependents must appoint their own. Once appointed, the representative will have power of attorney (POA) to assist with the dependentʼs VA claims.\n\nBefore appointing a representative, the dependentʼs relationship to the Veteran must be established. If a new representative is being appointed, the dependentʼs relationship to the Veteran will be validated first. The representative will be appointed to the dependent, not the Veteran.\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\nAfter you submit a disability compensation claim with the `POST /veterans/{veteranId}/526` synchronous endpoint, it is then established in Veterans Benefits Management System (VBMS). A `202` response means that the claim was successfully submitted by the API. However, it does not mean VA has received the required 526EZ PDF. \n\nTo confirm the status of your submission, use the `GET /veterans/{veteranId}/claims/{id}` endpoint and the ID returned with your submission response. 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. \n * For duplicate claims, the claim's progress is tracked under a different Claim ID than the one returned in your submission response. \n* **Claim received**: The claim was received, but hasn't been assigned to a reviewer yet.\n* **Initial review**: The claim has been assigned to a reviewer, who will determine if more information is needed.\n* **Evidence gathering, review, and decision**: VA is gathering evidence to make a decision from health care providers, government agencies, and other sources.\n* **Preparation for notification**: VA has made a decision on the claim, and is getting a decision letter ready to mail.\n* **Complete**: VA has sent a decision letter by U.S. mail.\n\n### Finding a Veteran's unique VA ID\n\nThis API uses Inegration Control Number (ICN) as a unique Veteran identifier to identify the subject of each API request. This identifier should be used as the `{veteranId}` parameter in request URLs.\n\n**Note**: though ICNs are typically static, they may change over time. If a specific ICN suddenly responds with a `404 not found` error, it may have changed. It’s a good idea to periodically check the ICN 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 claimant.\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## Appointing an accredited representative for dependents\n\nDependents of Veterans, such as spouses, children (biological and step), and parents (biological and foster) may be eligible for VA benefits and can request representation by an accredited representative.\n\nTo file claims through an accredited representative, dependents must appoint their own. Once appointed, the representative will have power of attorney (POA) to assist with the dependentʼs VA claims.\n\nBefore appointing a representative, the dependentʼs relationship to the Veteran must be established. If a new representative is being appointed, the dependentʼs relationship to the Veteran will be validated first. The representative will be appointed to the dependent, not the Veteran.\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\nAfter you submit a disability compensation claim with the `POST /veterans/{veteranId}/526/synchronous` endpoint, it is then established in Veterans Benefits Management System (VBMS). A `202` response means that the claim was successfully submitted by the API. However, it does not mean VA has received the required 526EZ PDF. \n\nTo confirm the status of your submission, use the `GET /veterans/{veteranId}/claims/{id}` endpoint and the ID returned with your submission response. 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. \n * For duplicate claims, the claim's progress is tracked under a different Claim ID than the one returned in your submission response. \n* **Claim received**: The claim was received, but hasn't been assigned to a reviewer yet.\n* **Initial review**: The claim has been assigned to a reviewer, who will determine if more information is needed.\n* **Evidence gathering, review, and decision**: VA is gathering evidence to make a decision from health care providers, government agencies, and other sources.\n* **Preparation for notification**: VA has made a decision on the claim, and is getting a decision letter ready to mail.\n* **Complete**: VA has sent a decision letter by U.S. mail.\n\n### Finding a Veteran's unique VA ID\n\nThis API uses Inegration Control Number (ICN) as a unique Veteran identifier to identify the subject of each API request. This identifier should be used as the `{veteranId}` parameter in request URLs.\n\n**Note**: though ICNs are typically static, they may change over time. If a specific ICN suddenly responds with a `404 not found` error, it may have changed. It’s a good idea to periodically check the ICN 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": [ { @@ -1036,7 +1036,7 @@ "202 without a transactionId": { "value": { "data": { - "id": "10dbad27-ce32-408e-885c-ec2abb34847e", + "id": "c04ea779-1c6a-4fd1-a3ba-f6953bc839cc", "type": "forms/526", "attributes": { "claimId": "600442191", @@ -1221,7 +1221,7 @@ }, "federalActivation": { "activationDate": "2023-10-01", - "anticipatedSeparationDate": "2024-12-15" + "anticipatedSeparationDate": "2024-12-18" }, "confinements": [ { @@ -1267,7 +1267,7 @@ "202 with a transactionId": { "value": { "data": { - "id": "11f513a7-ac0c-4ea9-b8ae-2600a34729cc", + "id": "739cbe6b-be83-4277-a3c6-8eced1eeb527", "type": "forms/526", "attributes": { "claimId": "600442191", @@ -1431,7 +1431,7 @@ "serviceBranch": "Public Health Service", "serviceComponent": "Active", "activeDutyBeginDate": "2008-11-14", - "activeDutyEndDate": "2024-12-15", + "activeDutyEndDate": "2024-12-18", "separationLocationCode": "98282" } ], @@ -1452,7 +1452,7 @@ }, "federalActivation": { "activationDate": "2023-10-01", - "anticipatedSeparationDate": "2024-12-15" + "anticipatedSeparationDate": "2024-12-18" }, "confinements": [ { @@ -4695,7 +4695,7 @@ "serviceBranch": "Public Health Service", "serviceComponent": "Active", "activeDutyBeginDate": "2008-11-14", - "activeDutyEndDate": "2024-12-15", + "activeDutyEndDate": "2024-12-18", "separationLocationCode": "98282" } ], @@ -4716,7 +4716,7 @@ }, "federalActivation": { "activationDate": "2023-10-01", - "anticipatedSeparationDate": "2024-12-15" + "anticipatedSeparationDate": "2024-12-18" }, "confinements": [ { @@ -9238,8 +9238,8 @@ "id": "1", "type": "intent_to_file", "attributes": { - "creationDate": "2024-12-13", - "expirationDate": "2025-12-13", + "creationDate": "2024-12-16", + "expirationDate": "2025-12-16", "type": "compensation", "status": "active" } @@ -10129,7 +10129,7 @@ "application/json": { "example": { "data": { - "id": "3f404e82-ed36-459c-9e43-a5a826655989", + "id": "8ecf04c6-588d-4d57-bd41-4ae0c54c7576", "type": "individual", "attributes": { "code": "067", @@ -10901,7 +10901,7 @@ "application/json": { "example": { "data": { - "id": "97cbb22e-90c9-4bce-a571-bbc54ff98e15", + "id": "c0ca13ea-5360-4b41-a8af-8f89a181c023", "type": "organization", "attributes": { "code": "083", @@ -12895,10 +12895,10 @@ "application/json": { "example": { "data": { - "id": "47bc11a3-a2c2-4d8a-8e46-253263642d6d", + "id": "18158bc1-2245-4262-a9a9-d955b93b40ea", "type": "claimsApiPowerOfAttorneys", "attributes": { - "dateRequestAccepted": "2024-12-13", + "dateRequestAccepted": "2024-12-16", "previousPoa": null, "representative": { "serviceOrganization": { @@ -13188,7 +13188,7 @@ "HIV", "ALCOHOLISM" ], - "id": "84da172b-2359-4198-9abf-44afbc7bdfde", + "id": "96fc2003-9ded-4a64-a7f2-798f3d018746", "type": "power-of-attorney-request" } } 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 07de9b8ebf7..e03ff977473 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 organization of individual with power of attorney for a claimant.\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## Appointing an accredited representative for dependents\n\nDependents of Veterans, such as spouses, children (biological and step), and parents (biological and foster) may be eligible for VA benefits and can request representation by an accredited representative.\n\nTo file claims through an accredited representative, dependents must appoint their own. Once appointed, the representative will have power of attorney (POA) to assist with the dependentʼs VA claims.\n\nBefore appointing a representative, the dependentʼs relationship to the Veteran must be established. If a new representative is being appointed, the dependentʼs relationship to the Veteran will be validated first. The representative will be appointed to the dependent, not the Veteran.\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\nAfter you submit a disability compensation claim with the `POST /veterans/{veteranId}/526` synchronous endpoint, it is then established in Veterans Benefits Management System (VBMS). A `202` response means that the claim was successfully submitted by the API. However, it does not mean VA has received the required 526EZ PDF. \n\nTo confirm the status of your submission, use the `GET /veterans/{veteranId}/claims/{id}` endpoint and the ID returned with your submission response. 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. \n * For duplicate claims, the claim's progress is tracked under a different Claim ID than the one returned in your submission response. \n* **Claim received**: The claim was received, but hasn't been assigned to a reviewer yet.\n* **Initial review**: The claim has been assigned to a reviewer, who will determine if more information is needed.\n* **Evidence gathering, review, and decision**: VA is gathering evidence to make a decision from health care providers, government agencies, and other sources.\n* **Preparation for notification**: VA has made a decision on the claim, and is getting a decision letter ready to mail.\n* **Complete**: VA has sent a decision letter by U.S. mail.\n\n### Finding a Veteran's unique VA ID\n\nThis API uses Inegration Control Number (ICN) as a unique Veteran identifier to identify the subject of each API request. This identifier should be used as the `{veteranId}` parameter in request URLs.\n\n**Note**: though ICNs are typically static, they may change over time. If a specific ICN suddenly responds with a `404 not found` error, it may have changed. It’s a good idea to periodically check the ICN 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 claimant.\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## Appointing an accredited representative for dependents\n\nDependents of Veterans, such as spouses, children (biological and step), and parents (biological and foster) may be eligible for VA benefits and can request representation by an accredited representative.\n\nTo file claims through an accredited representative, dependents must appoint their own. Once appointed, the representative will have power of attorney (POA) to assist with the dependentʼs VA claims.\n\nBefore appointing a representative, the dependentʼs relationship to the Veteran must be established. If a new representative is being appointed, the dependentʼs relationship to the Veteran will be validated first. The representative will be appointed to the dependent, not the Veteran.\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\nAfter you submit a disability compensation claim with the `POST /veterans/{veteranId}/526/synchronous` endpoint, it is then established in Veterans Benefits Management System (VBMS). A `202` response means that the claim was successfully submitted by the API. However, it does not mean VA has received the required 526EZ PDF. \n\nTo confirm the status of your submission, use the `GET /veterans/{veteranId}/claims/{id}` endpoint and the ID returned with your submission response. 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. \n * For duplicate claims, the claim's progress is tracked under a different Claim ID than the one returned in your submission response. \n* **Claim received**: The claim was received, but hasn't been assigned to a reviewer yet.\n* **Initial review**: The claim has been assigned to a reviewer, who will determine if more information is needed.\n* **Evidence gathering, review, and decision**: VA is gathering evidence to make a decision from health care providers, government agencies, and other sources.\n* **Preparation for notification**: VA has made a decision on the claim, and is getting a decision letter ready to mail.\n* **Complete**: VA has sent a decision letter by U.S. mail.\n\n### Finding a Veteran's unique VA ID\n\nThis API uses Inegration Control Number (ICN) as a unique Veteran identifier to identify the subject of each API request. This identifier should be used as the `{veteranId}` parameter in request URLs.\n\n**Note**: though ICNs are typically static, they may change over time. If a specific ICN suddenly responds with a `404 not found` error, it may have changed. It’s a good idea to periodically check the ICN 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": [ { @@ -335,7 +335,7 @@ "202 without a transactionId": { "value": { "data": { - "id": "b2d0e853-3c05-4cda-aff5-9edc47a9db98", + "id": "75be0d19-4649-4b81-a06c-b99e6b176e7f", "type": "forms/526", "attributes": { "claimId": "600442191", @@ -520,7 +520,7 @@ }, "federalActivation": { "activationDate": "2023-10-01", - "anticipatedSeparationDate": "2024-12-15" + "anticipatedSeparationDate": "2024-12-18" }, "confinements": [ { @@ -566,7 +566,7 @@ "202 with a transactionId": { "value": { "data": { - "id": "52934ea0-ec73-4ee6-b755-0b8f68d76b64", + "id": "f2c7cfd4-277b-4537-9b8d-ea53024b111e", "type": "forms/526", "attributes": { "claimId": "600442191", @@ -730,7 +730,7 @@ "serviceBranch": "Public Health Service", "serviceComponent": "Active", "activeDutyBeginDate": "2008-11-14", - "activeDutyEndDate": "2024-12-15", + "activeDutyEndDate": "2024-12-18", "separationLocationCode": "98282" } ], @@ -751,7 +751,7 @@ }, "federalActivation": { "activationDate": "2023-10-01", - "anticipatedSeparationDate": "2024-12-15" + "anticipatedSeparationDate": "2024-12-18" }, "confinements": [ { @@ -3994,7 +3994,7 @@ "serviceBranch": "Public Health Service", "serviceComponent": "Active", "activeDutyBeginDate": "2008-11-14", - "activeDutyEndDate": "2024-12-15", + "activeDutyEndDate": "2024-12-18", "separationLocationCode": "98282" } ], @@ -4015,7 +4015,7 @@ }, "federalActivation": { "activationDate": "2023-10-01", - "anticipatedSeparationDate": "2024-12-15" + "anticipatedSeparationDate": "2024-12-18" }, "confinements": [ { @@ -8537,8 +8537,8 @@ "id": "1", "type": "intent_to_file", "attributes": { - "creationDate": "2024-12-13", - "expirationDate": "2025-12-13", + "creationDate": "2024-12-16", + "expirationDate": "2025-12-16", "type": "compensation", "status": "active" } @@ -9428,7 +9428,7 @@ "application/json": { "example": { "data": { - "id": "c3f520bb-f029-4246-8c18-3e07bf5ceb24", + "id": "3fb853d6-fe4f-4593-9ae4-b44d866cb3b5", "type": "individual", "attributes": { "code": "067", @@ -10200,7 +10200,7 @@ "application/json": { "example": { "data": { - "id": "91613d64-0737-4297-8cb1-6290f0b8f0c7", + "id": "20da46c4-10d5-4985-8113-db92597ac85f", "type": "organization", "attributes": { "code": "083", @@ -12194,10 +12194,10 @@ "application/json": { "example": { "data": { - "id": "533c94c4-5bd6-4ff3-b539-c968525d3784", + "id": "38b49aa2-1163-4b42-9bc1-331a42404117", "type": "claimsApiPowerOfAttorneys", "attributes": { - "dateRequestAccepted": "2024-12-13", + "dateRequestAccepted": "2024-12-16", "previousPoa": null, "representative": { "serviceOrganization": { diff --git a/modules/ivc_champva/app/services/ivc_champva/attachments.rb b/modules/ivc_champva/app/services/ivc_champva/attachments.rb index f5e5627f192..60bb476d2ed 100644 --- a/modules/ivc_champva/app/services/ivc_champva/attachments.rb +++ b/modules/ivc_champva/app/services/ivc_champva/attachments.rb @@ -4,7 +4,7 @@ module IvcChampva module Attachments attr_accessor :form_id, :uuid, :data - def handle_attachments(file_path) + def handle_attachments(file_path) # rubocop:disable Metrics/MethodLength file_paths = [file_path] attachments = get_attachments @@ -22,7 +22,14 @@ def handle_attachments(file_path) File.join(File.dirname(attachment), new_file_name) end - File.rename(attachment, new_file_path) + if Flipper.enabled?(:champva_pdf_decrypt, @current_user) + # Use FileUtils.mv to handle `Errno::EXDEV` error since encrypted PDFs + # get stashed in the clamav_tmp dir which is a different device on staging, apparently + FileUtils.mv(attachment, new_file_path) # Performs a copy automatically if mv fails + else + File.rename(attachment, new_file_path) + end + file_paths << new_file_path end diff --git a/modules/simple_forms_api/app/services/simple_forms_api/notification_email.rb b/modules/simple_forms_api/app/services/simple_forms_api/notification_email.rb index 6f6c3513818..fed3cd12bd3 100644 --- a/modules/simple_forms_api/app/services/simple_forms_api/notification_email.rb +++ b/modules/simple_forms_api/app/services/simple_forms_api/notification_email.rb @@ -270,7 +270,9 @@ def get_first_name_from_user def get_personalization(first_name) if @form_number.start_with? 'vba_21_0966' - default_personalization(first_name).merge(form21_0966_personalization) + personalization = default_personalization(first_name).merge(form21_0966_personalization) + personalization.except!('lighthouse_updated_at') unless lighthouse_updated_at + personalization else default_personalization(first_name) end @@ -325,7 +327,7 @@ def form20_10207_contact_info def form21_0845_contact_info # (vet && signed in) if @form_data['authorizer_type'] == 'veteran' - [@form_data['authorizer_email'] || @user&.va_profile_email, @form_data.dig('veteran_full_name', 'first')] + [@form_data['veteran_email'] || @user&.va_profile_email, @form_data.dig('veteran_full_name', 'first')] # (non-vet && signed in) || (non-vet && anon) elsif @form_data['authorizer_type'] == 'nonVeteran' diff --git a/modules/simple_forms_api/spec/requests/simple_forms_api/v1/simple_forms_spec.rb b/modules/simple_forms_api/spec/requests/simple_forms_api/v1/simple_forms_spec.rb index 5ae60c00e99..cd1b8cfc624 100644 --- a/modules/simple_forms_api/spec/requests/simple_forms_api/v1/simple_forms_spec.rb +++ b/modules/simple_forms_api/spec/requests/simple_forms_api/v1/simple_forms_spec.rb @@ -916,7 +916,6 @@ 'first_name' => 'Veteran', 'date_submitted' => Time.zone.today.strftime('%B %d, %Y'), 'confirmation_number' => confirmation_number, - 'lighthouse_updated_at' => nil, 'intent_to_file_benefits' => 'survivors pension benefits', 'intent_to_file_benefits_links' => '[Apply for DIC, Survivors Pension, and/or Accrued Benefits ' \ '(VA Form 21P-534EZ)](https://www.va.gov/find-forms/about-form-21p-534ez/)', @@ -942,7 +941,6 @@ 'first_name' => 'Veteran', 'date_submitted' => Time.zone.today.strftime('%B %d, %Y'), 'confirmation_number' => confirmation_number, - 'lighthouse_updated_at' => nil, 'intent_to_file_benefits' => 'survivors pension benefits', 'intent_to_file_benefits_links' => '[Apply for DIC, Survivors Pension, and/or Accrued Benefits ' \ '(VA Form 21P-534EZ)](https://www.va.gov/find-forms/about-form-21p-534ez/)', diff --git a/modules/simple_forms_api/spec/services/notification_email_spec.rb b/modules/simple_forms_api/spec/services/notification_email_spec.rb index 8906192ea82..a817c0c946d 100644 --- a/modules/simple_forms_api/spec/services/notification_email_spec.rb +++ b/modules/simple_forms_api/spec/services/notification_email_spec.rb @@ -691,14 +691,14 @@ it 'veteran authorizer' do allow(VANotify::EmailJob).to receive(:perform_async) data['authorizer_type'] = 'veteran' - data['authorizer_email'] = 'authorizer_email@example.com' + data['veteran_email'] = 'veteran_email@example.com' subject = described_class.new(config, user: create(:user)) subject.send expect(VANotify::EmailJob).to have_received(:perform_async).with( - 'authorizer_email@example.com', + 'veteran_email@example.com', 'form21_0845_confirmation_email_template_id', { 'first_name' => 'John', @@ -785,6 +785,7 @@ end describe '21_0966' do + let(:lighthouse_updated_at) { 1.day.ago } let(:date_submitted) { Time.zone.today.strftime('%B %d, %Y') } let(:data) do fixture_path = Rails.root.join( @@ -794,7 +795,7 @@ end let(:config) do { form_data: data, form_number: 'vba_21_0966', - confirmation_number: 'confirmation_number', date_submitted: } + confirmation_number: 'confirmation_number', date_submitted:, lighthouse_updated_at: } end let(:user) { create(:user, :loa3) } @@ -812,7 +813,7 @@ 'first_name' => 'Veteran', 'date_submitted' => date_submitted, 'confirmation_number' => 'confirmation_number', - 'lighthouse_updated_at' => nil, + 'lighthouse_updated_at' => lighthouse_updated_at, 'intent_to_file_benefits' => 'survivors pension benefits', 'intent_to_file_benefits_links' => '[Apply for DIC, Survivors Pension, and/or Accrued Benefits ' \ '(VA Form 21P-534EZ)](https://www.va.gov/find-forms/about-form-21p-534ez/)', @@ -841,7 +842,7 @@ 'first_name' => 'I', 'date_submitted' => date_submitted, 'confirmation_number' => 'confirmation_number', - 'lighthouse_updated_at' => nil, + 'lighthouse_updated_at' => lighthouse_updated_at, 'intent_to_file_benefits' => 'survivors pension benefits', 'intent_to_file_benefits_links' => '[Apply for DIC, Survivors Pension, and/or Accrued Benefits ' \ '(VA Form 21P-534EZ)](https://www.va.gov/find-forms/about-form-21p-534ez/)', @@ -884,7 +885,6 @@ 'first_name' => 'Veteran', 'date_submitted' => date_submitted, 'confirmation_number' => 'confirmation_number', - 'lighthouse_updated_at' => nil, 'intent_to_file_benefits' => 'survivors pension benefits', 'intent_to_file_benefits_links' => '[Apply for DIC, Survivors Pension, and/or Accrued Benefits ' \ '(VA Form 21P-534EZ)](https://www.va.gov/find-forms/about-form-21p-534ez/)', @@ -913,7 +913,6 @@ 'first_name' => 'I', 'date_submitted' => date_submitted, 'confirmation_number' => 'confirmation_number', - 'lighthouse_updated_at' => nil, 'intent_to_file_benefits' => 'survivors pension benefits', 'intent_to_file_benefits_links' => '[Apply for DIC, Survivors Pension, and/or Accrued Benefits ' \ '(VA Form 21P-534EZ)](https://www.va.gov/find-forms/about-form-21p-534ez/)', diff --git a/modules/vaos/app/services/vaos/v2/appointments_service.rb b/modules/vaos/app/services/vaos/v2/appointments_service.rb index a425a300287..ed56dab10cc 100644 --- a/modules/vaos/app/services/vaos/v2/appointments_service.rb +++ b/modules/vaos/app/services/vaos/v2/appointments_service.rb @@ -514,6 +514,20 @@ def filter_reason_code_text(request_object_body) VAOS::Strings.filter_ascii_characters(text) if text.present? end + # Determines if the appointment is a Cerner (Oracle Health) appointment. + # This is determined by the presence of a 'CERN' prefix in the appointment's id. + # + # @param appt [Hash] the appointment to check + # @return [Boolean] true if the appointment is a Cerner appointment, false otherwise + # + # @raise [ArgumentError] if the appointment is nil + # + def cerner?(appt) + raise ArgumentError, 'Appointment cannot be nil' if appt.nil? + + appt[:id].start_with?('CERN') + end + # Checks if the appointment is booked. # # @param appt [Hash] the appointment to check @@ -691,22 +705,47 @@ def log_direct_schedule_submission_errors(e) end def set_type(appointment) - type = APPOINTMENT_TYPES[:request] if appointment[:kind] != 'cc' && appointment[:request_periods].present? - - type ||= case appointment[:kind] - when 'cc' - if appointment[:start] - APPOINTMENT_TYPES[:cc_appointment] - else - APPOINTMENT_TYPES[:cc_request] - end - else - APPOINTMENT_TYPES[:va] - end + type = if cerner?(appointment) + cerner_type(appointment) + else + non_cerner_type(appointment) + end appointment[:type] = type end + # Determines the type of appointment for Cerner appointments. + # @param appointment [Hash] the appointment to determine the type for + # + # @return [String] the type of appointment + # + def cerner_type(appointment) + if appointment[:end].present? + appointment[:kind] == 'cc' ? APPOINTMENT_TYPES[:cc_appointment] : APPOINTMENT_TYPES[:va] + else + appointment[:kind] == 'cc' ? APPOINTMENT_TYPES[:cc_request] : APPOINTMENT_TYPES[:request] + end + end + + # Determines the type of appointment for non-Cerner appointments. + # @param appointment [Hash] the appointment to determine the type for + # + # @return [String] the type of appointment + # + def non_cerner_type(appointment) + if appointment[:kind] == 'cc' + if appointment[:requested_periods].present? + APPOINTMENT_TYPES[:cc_request] + else + APPOINTMENT_TYPES[:cc_appointment] + end + elsif appointment[:requested_periods].present? + APPOINTMENT_TYPES[:request] + else + APPOINTMENT_TYPES[:va] + end + end + # Modifies the appointment, setting the cancellable flag to false # # @param appointment [Hash] the appointment to modify diff --git a/modules/vaos/spec/services/v2/appointment_service_spec.rb b/modules/vaos/spec/services/v2/appointment_service_spec.rb index 2ad2e63989f..8db7f4ed65a 100644 --- a/modules/vaos/spec/services/v2/appointment_service_spec.rb +++ b/modules/vaos/spec/services/v2/appointment_service_spec.rb @@ -1197,6 +1197,20 @@ end end + describe '#cerner?' do + it 'raises an ArgumentError if appt is nil' do + expect { subject.send(:cerner?, nil) }.to raise_error(ArgumentError, 'Appointment cannot be nil') + end + + it 'returns true for appointments with a "CERN" prefix' do + expect(subject.send(:cerner?, { id: 'CERN99999' })).to eq(true) + end + + it 'returns false for appointments without a "CERN" prefix' do + expect(subject.send(:cerner?, { id: '99999' })).to eq(false) + end + end + describe '#no_service_cat?' do it 'raises an ArgumentError if appt is nil' do expect { subject.send(:no_service_cat?, nil) }.to raise_error(ArgumentError, 'Appointment cannot be nil') @@ -1643,4 +1657,70 @@ expect(appt[:preferred_dates]).not_to be_nil end end + + describe '#set_type' do + it 'has a type of request for Cerner appointments without end dates' do + appt = FactoryBot.build(:appointment_form_v2, :va_proposed_valid_reason_code_text).attributes + appt[:id] = 'CERN1234' + appt[:end] = nil + subject.send(:set_type, appt) + expect(appt[:type]).to eq('REQUEST') + end + + it 'is a VA appointment for Cerner appointments with a valid end date' do + appt = FactoryBot.build(:appointment_form_v2, :va_proposed_valid_reason_code_text).attributes + appt[:id] = 'CERN1234' + appt[:end] = :end_date + subject.send(:set_type, appt) + expect(appt[:type]).to eq('VA') + end + + it 'is a cc appointment for appointments with kind = "cc" and a valid start date' do + appt = FactoryBot.build(:appointment_form_v2, :va_proposed_valid_reason_code_text).attributes + appt[:id] = :id + appt[:start] = :start_date + appt[:requested_periods] = [] + appt[:kind] = 'cc' + subject.send(:set_type, appt) + expect(appt[:type]).to eq('COMMUNITY_CARE_APPOINTMENT') + end + + it 'is a cc request for appointments with kind = "cc" and at least one requested period' do + appt = FactoryBot.build(:appointment_form_v2, :va_proposed_valid_reason_code_text).attributes + appt[:id] = :id + appt[:kind] = 'cc' + appt[:requested_periods] = [{ start: '2024-06-26T12:00:00Z', end: '2024-06-26T13:00:00Z' }] + subject.send(:set_type, appt) + expect(appt[:type]).to eq('COMMUNITY_CARE_REQUEST') + end + + it 'is a request for appointments with kind other than "cc" and at least one requested period' do + appt = FactoryBot.build(:appointment_form_v2, :va_proposed_valid_reason_code_text).attributes + appt[:id] = :id + appt[:kind] = 'telehealth' + appt[:requested_periods] = [{ start: '2024-06-26T12:00:00Z', end: '2024-06-26T13:00:00Z' }] + subject.send(:set_type, appt) + expect(appt[:type]).to eq('REQUEST') + end + + it 'is a request for appointments with kind = "cc" and no start date or requested periods' do + appt = FactoryBot.build(:appointment_form_v2, :va_proposed_valid_reason_code_text).attributes + appt[:id] = :id + appt[:kind] = 'cc' + appt[:start] = nil + appt[:requested_periods] = [] + subject.send(:set_type, appt) + expect(appt[:type]).to eq('COMMUNITY_CARE_APPOINTMENT') + end + + it 'is a cc request for Cerner with no start date or requested periods' do + appt = FactoryBot.build(:appointment_form_v2, :va_proposed_valid_reason_code_text).attributes + appt[:id] = 'CERN1234' + appt[:kind] = 'cc' + appt[:start] = nil + appt[:requested_periods] = [] + subject.send(:set_type, appt) + expect(appt[:type]).to eq('COMMUNITY_CARE_REQUEST') + end + end end diff --git a/modules/vye/app/sidekiq/vye/dawn_dash/activate_bdn.rb b/modules/vye/app/sidekiq/vye/dawn_dash/activate_bdn.rb index 3e9c63e7e22..b964dac5a9c 100644 --- a/modules/vye/app/sidekiq/vye/dawn_dash/activate_bdn.rb +++ b/modules/vye/app/sidekiq/vye/dawn_dash/activate_bdn.rb @@ -7,6 +7,8 @@ class ActivateBdn sidekiq_options retry: 0 def perform + return if Vye::CloudTransfer.holiday? + BdnClone.activate_injested! EgressUpdates.perform_async end diff --git a/modules/vye/app/sidekiq/vye/dawn_dash/egress_updates.rb b/modules/vye/app/sidekiq/vye/dawn_dash/egress_updates.rb index 8aecc650f95..ed5b3b724af 100644 --- a/modules/vye/app/sidekiq/vye/dawn_dash/egress_updates.rb +++ b/modules/vye/app/sidekiq/vye/dawn_dash/egress_updates.rb @@ -7,6 +7,8 @@ class EgressUpdates sidekiq_options retry: 0 def perform + return if Vye::CloudTransfer.holiday? + Vye::BatchTransfer::EgressFiles.address_changes_upload Vye::BatchTransfer::EgressFiles.direct_deposit_upload Vye::BatchTransfer::EgressFiles.verification_upload diff --git a/modules/vye/app/sidekiq/vye/midnight_run/ingress_bdn.rb b/modules/vye/app/sidekiq/vye/midnight_run/ingress_bdn.rb index 34e92e2f28d..c3aaf046180 100644 --- a/modules/vye/app/sidekiq/vye/midnight_run/ingress_bdn.rb +++ b/modules/vye/app/sidekiq/vye/midnight_run/ingress_bdn.rb @@ -7,6 +7,8 @@ class IngressBdn sidekiq_options retry: 5 def perform + return if Vye::CloudTransfer.holiday? + bdn_clone = Vye::BdnClone.create!(transact_date: Time.zone.today) bdn_clone_id = bdn_clone.id diff --git a/modules/vye/app/sidekiq/vye/midnight_run/ingress_tims.rb b/modules/vye/app/sidekiq/vye/midnight_run/ingress_tims.rb index 0fabaee9df7..726cbcf67c5 100644 --- a/modules/vye/app/sidekiq/vye/midnight_run/ingress_tims.rb +++ b/modules/vye/app/sidekiq/vye/midnight_run/ingress_tims.rb @@ -7,6 +7,8 @@ class IngressTims sidekiq_options retry: 0 def perform + return if Vye::CloudTransfer.holiday? + Vye::PendingDocument.delete_all chunks = BatchTransfer::TimsChunk.build_chunks diff --git a/modules/vye/app/sidekiq/vye/sundown_sweep.rb b/modules/vye/app/sidekiq/vye/sundown_sweep.rb index 496dd15c74e..e8fd34b62dc 100644 --- a/modules/vye/app/sidekiq/vye/sundown_sweep.rb +++ b/modules/vye/app/sidekiq/vye/sundown_sweep.rb @@ -7,7 +7,6 @@ class SundownSweep def perform ClearDeactivatedBdns.perform_async DeleteProcessedS3Files.perform_async - PurgeStaleVerifications.perform_async end end end diff --git a/modules/vye/app/sidekiq/vye/sundown_sweep/clear_deactivated_bdns.rb b/modules/vye/app/sidekiq/vye/sundown_sweep/clear_deactivated_bdns.rb index 5a8b90efd51..e34a009a957 100644 --- a/modules/vye/app/sidekiq/vye/sundown_sweep/clear_deactivated_bdns.rb +++ b/modules/vye/app/sidekiq/vye/sundown_sweep/clear_deactivated_bdns.rb @@ -6,6 +6,8 @@ class ClearDeactivatedBdns include Sidekiq::Worker def perform + return if Vye::CloudTransfer.holiday? + logger.info('Beginning: delete deactivated bdns') Vye::CloudTransfer.delete_inactive_bdns logger.info('Finishing: delete deactivated bdns') diff --git a/modules/vye/app/sidekiq/vye/sundown_sweep/delete_processed_s3_files.rb b/modules/vye/app/sidekiq/vye/sundown_sweep/delete_processed_s3_files.rb index 8ba929216e2..9dbeb7c57c8 100644 --- a/modules/vye/app/sidekiq/vye/sundown_sweep/delete_processed_s3_files.rb +++ b/modules/vye/app/sidekiq/vye/sundown_sweep/delete_processed_s3_files.rb @@ -5,6 +5,8 @@ class SundownSweep class DeleteProcessedS3Files include Sidekiq::Worker def perform + return if Vye::CloudTransfer.holiday? + logger.info('Beginning: remove_aws_files_from_s3_buckets') Vye::CloudTransfer.remove_aws_files_from_s3_buckets logger.info('Finishing: remove_aws_files_from_s3_buckets') diff --git a/modules/vye/app/sidekiq/vye/sundown_sweep/purge_stale_verifications.rb b/modules/vye/app/sidekiq/vye/sundown_sweep/purge_stale_verifications.rb deleted file mode 100644 index 956a6826578..00000000000 --- a/modules/vye/app/sidekiq/vye/sundown_sweep/purge_stale_verifications.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -module Vye - class SundownSweep - class PurgeStaleVerifications - include Sidekiq::Worker - - def perform; end - end - end -end diff --git a/modules/vye/lib/concerns/vye/cloud_transfer.rb b/modules/vye/lib/concerns/vye/cloud_transfer.rb index 3621e0b3689..1750822b4ef 100644 --- a/modules/vye/lib/concerns/vye/cloud_transfer.rb +++ b/modules/vye/lib/concerns/vye/cloud_transfer.rb @@ -20,6 +20,10 @@ def s3_client Aws::S3::Client.new(**credentials) end + def holiday? + Holidays.on(Time.zone.today, :us, :observed).any? + end + def tmp_dir result = Rails.root / "tmp/vye/#{SecureRandom.uuid}" result.mkpath diff --git a/modules/vye/spec/sidekiq/vye/dawn_dash/activate_bdn_spec.rb b/modules/vye/spec/sidekiq/vye/dawn_dash/activate_bdn_spec.rb index de0392c56f5..87031e03cac 100644 --- a/modules/vye/spec/sidekiq/vye/dawn_dash/activate_bdn_spec.rb +++ b/modules/vye/spec/sidekiq/vye/dawn_dash/activate_bdn_spec.rb @@ -1,22 +1,48 @@ # frozen_string_literal: true require 'rails_helper' -require Vye::Engine.root / 'spec/rails_helper' +require 'timecop' -describe Vye::DawnDash::ActivateBdn, type: :worker do +describe Vye::SundownSweep::DeleteProcessedS3Files, type: :worker do before do - Sidekiq::Worker.clear_all + Sidekiq::Job.clear_all end - it 'enqueues child jobs' do - expect(Vye::BdnClone).to receive(:activate_injested!) + context 'when it is not a holiday' do + before do + Timecop.freeze(Time.zone.local(2024, 7, 2)) # Regular work day + end - expect do - described_class.perform_async - end.to change { Sidekiq::Worker.jobs.size }.by(1) + after do + Timecop.return + end - described_class.drain + it 'checks the existence of described_class' do + expect(Vye::CloudTransfer).to receive(:remove_aws_files_from_s3_buckets) - expect(Vye::DawnDash::EgressUpdates).to have_enqueued_sidekiq_job + expect do + described_class.perform_async + end.to change { Sidekiq::Worker.jobs.size }.by(1) + + described_class.drain + end + end + + context 'when it is a holiday' do + before do + Timecop.freeze(Time.zone.local(2024, 7, 4)) # Independence Day + end + + after do + Timecop.return + end + + it 'does not process S3 files' do + expect(Vye::CloudTransfer).not_to receive(:remove_aws_files_from_s3_buckets) + + expect do + described_class.new.perform + end.not_to(change { Sidekiq::Worker.jobs.size }) + end end end diff --git a/modules/vye/spec/sidekiq/vye/dawn_dash/egress_updates_spec.rb b/modules/vye/spec/sidekiq/vye/dawn_dash/egress_updates_spec.rb index 47abe23e86d..2159dc58586 100644 --- a/modules/vye/spec/sidekiq/vye/dawn_dash/egress_updates_spec.rb +++ b/modules/vye/spec/sidekiq/vye/dawn_dash/egress_updates_spec.rb @@ -2,22 +2,54 @@ require 'rails_helper' require Vye::Engine.root / 'spec/rails_helper' +require 'timecop' describe Vye::DawnDash::EgressUpdates, type: :worker do before do Sidekiq::Worker.clear_all end - it 'checks the existence of described_class' do - expect(Vye::BatchTransfer::EgressFiles).to receive(:address_changes_upload) - expect(Vye::BatchTransfer::EgressFiles).to receive(:direct_deposit_upload) - expect(Vye::BatchTransfer::EgressFiles).to receive(:verification_upload) - expect(Vye::BdnClone).to receive(:clear_export_ready!) + context 'when it is not a holiday' do + before do + Timecop.freeze(Time.zone.local(2024, 7, 2)) # Regular work day + end - expect do - described_class.perform_async - end.to change { Sidekiq::Worker.jobs.size }.by(1) + after do + Timecop.return + end - described_class.drain + it 'checks the existence of described_class' do + expect(Vye::BatchTransfer::EgressFiles).to receive(:address_changes_upload) + expect(Vye::BatchTransfer::EgressFiles).to receive(:direct_deposit_upload) + expect(Vye::BatchTransfer::EgressFiles).to receive(:verification_upload) + expect(Vye::BdnClone).to receive(:clear_export_ready!) + + expect do + described_class.perform_async + end.to change { Sidekiq::Worker.jobs.size }.by(1) + + described_class.drain + end + end + + context 'when it is a holiday' do + before do + Timecop.freeze(Time.zone.local(2024, 7, 4)) # Independence Day + end + + after do + Timecop.return + end + + it 'does not process egress updates' do + expect(Vye::BatchTransfer::EgressFiles).not_to receive(:address_changes_upload) + expect(Vye::BatchTransfer::EgressFiles).not_to receive(:direct_deposit_upload) + expect(Vye::BatchTransfer::EgressFiles).not_to receive(:verification_upload) + expect(Vye::BdnClone).not_to receive(:clear_export_ready!) + + expect do + described_class.new.perform + end.not_to(change { Sidekiq::Worker.jobs.size }) + end end end diff --git a/modules/vye/spec/sidekiq/vye/midnight_run/ingress_bdn_spec.rb b/modules/vye/spec/sidekiq/vye/midnight_run/ingress_bdn_spec.rb index aa4885c6740..9966334d128 100644 --- a/modules/vye/spec/sidekiq/vye/midnight_run/ingress_bdn_spec.rb +++ b/modules/vye/spec/sidekiq/vye/midnight_run/ingress_bdn_spec.rb @@ -2,6 +2,7 @@ require 'rails_helper' require Vye::Engine.root / 'spec/rails_helper' +require 'timecop' describe Vye::MidnightRun::IngressBdn, type: :worker do let(:bdn_clone) { FactoryBot.create(:vye_bdn_clone_base) } @@ -18,16 +19,43 @@ Sidekiq::Job.clear_all end - it 'checks the existence of described_class' do - expect(Vye::BdnClone).to receive(:create!).and_return(bdn_clone) - expect(Vye::BatchTransfer::BdnChunk).to receive(:build_chunks).and_return(chunks) + context 'when it is not a holiday' do + before do + Timecop.freeze(Time.zone.local(2024, 7, 2)) # Regular work day + end + + after do + Timecop.return + end + + it 'checks the existence of described_class' do + expect(Vye::BdnClone).to receive(:create!).and_return(bdn_clone) + expect(Vye::BatchTransfer::BdnChunk).to receive(:build_chunks).and_return(chunks) - expect do - described_class.perform_async - end.to change { Sidekiq::Worker.jobs.size }.by(1) + expect do + described_class.perform_async + end.to change { Sidekiq::Worker.jobs.size }.by(1) - described_class.drain + described_class.drain + + expect(Vye::MidnightRun::IngressBdnChunk).to have_enqueued_sidekiq_job.exactly(5).times + end + end - expect(Vye::MidnightRun::IngressBdnChunk).to have_enqueued_sidekiq_job.exactly(5).times + context 'when it is a holiday' do + before do + Timecop.freeze(Time.zone.local(2024, 7, 4)) # Independence Day + end + + after do + Timecop.return + end + + it 'does not process BDNs' do + expect(Vye::BdnClone).not_to receive(:create!) + expect(Vye::BatchTransfer::BdnChunk).not_to receive(:build_chunks) + + described_class.new.perform + end end end diff --git a/modules/vye/spec/sidekiq/vye/midnight_run/ingress_tims_spec.rb b/modules/vye/spec/sidekiq/vye/midnight_run/ingress_tims_spec.rb index f0e2aed0296..8d0b03b728d 100644 --- a/modules/vye/spec/sidekiq/vye/midnight_run/ingress_tims_spec.rb +++ b/modules/vye/spec/sidekiq/vye/midnight_run/ingress_tims_spec.rb @@ -2,6 +2,7 @@ require 'rails_helper' require Vye::Engine.root / 'spec/rails_helper' +require 'timecop' describe Vye::MidnightRun::IngressTims, type: :worker do let(:chunks) do @@ -17,15 +18,43 @@ Sidekiq::Job.clear_all end - it 'checks the existence of described_class' do - expect(Vye::BatchTransfer::TimsChunk).to receive(:build_chunks).and_return(chunks) + context 'when it is not a holiday' do + before do + Timecop.freeze(Time.zone.local(2024, 7, 2)) # Regular work day + end + + after do + Timecop.return + end + + it 'checks the existence of described_class' do + expect(Vye::BatchTransfer::TimsChunk).to receive(:build_chunks).and_return(chunks) - expect do - described_class.perform_async - end.to change { Sidekiq::Worker.jobs.size }.by(1) + expect do + described_class.perform_async + end.to change { Sidekiq::Worker.jobs.size }.by(1) - described_class.drain + described_class.drain + + expect(Vye::MidnightRun::IngressTimsChunk).to have_enqueued_sidekiq_job.exactly(5).times + end + end - expect(Vye::MidnightRun::IngressTimsChunk).to have_enqueued_sidekiq_job.exactly(5).times + context 'when it is a holiday' do + before do + Timecop.freeze(Time.zone.local(2024, 7, 4)) # Independence Day + end + + after do + Timecop.return + end + + it 'does not process TIMS' do + expect(Vye::BatchTransfer::TimsChunk).not_to receive(:build_chunks) + + expect do + described_class.new.perform + end.not_to(change { Sidekiq::Worker.jobs.size }) + end end end diff --git a/modules/vye/spec/sidekiq/vye/sundown_sweep/clear_deactivated_bdn_spec.rb b/modules/vye/spec/sidekiq/vye/sundown_sweep/clear_deactivated_bdn_spec.rb index 8bc0466e215..e70c81b54b1 100644 --- a/modules/vye/spec/sidekiq/vye/sundown_sweep/clear_deactivated_bdn_spec.rb +++ b/modules/vye/spec/sidekiq/vye/sundown_sweep/clear_deactivated_bdn_spec.rb @@ -2,61 +2,90 @@ require 'rails_helper' require Vye::Engine.root / 'spec/rails_helper' +require 'timecop' describe Vye::SundownSweep::ClearDeactivatedBdns, type: :worker do before do Sidekiq::Job.clear_all end - it 'checks the existence of described_class' do - expect(Vye::CloudTransfer).to receive(:delete_inactive_bdns) + context 'when it is not a holiday' do + before do + Timecop.freeze(Time.zone.local(2024, 7, 2)) # Regular work day + end - expect do - described_class.perform_async - end.to change { Sidekiq::Worker.jobs.size }.by(1) + after do + Timecop.return + end - described_class.drain - end + it 'checks the existence of described_class' do + expect(Vye::CloudTransfer).to receive(:delete_inactive_bdns) + + expect do + described_class.perform_async + end.to change { Sidekiq::Worker.jobs.size }.by(1) + + described_class.drain + end + + # direct deposit changes, addy changes and awards are deleted + # verifications have their foreign keys set to null + it 'deletes inactive BDNs and processes their ri children correctly' do + # create one active and one inactive BDN + create(:vye_bdn_clone_with_user_info_children) + create(:vye_bdn_clone_with_user_info_children, :active) + + # rubocop:disable RSpec/ChangeByZero + expect do + described_class.new.perform + end.to change(Vye::BdnClone, :count) + .by(-1) + .and change(Vye::AddressChange, :count).by(-3) # 1 addy's created initially + .and change(Vye::Award, :count).by(-4) + .and change(Vye::DirectDepositChange, :count).by(-2) + .and change(Vye::Verification, :count).by(0) + .and change(Vye::Verification.where(user_info_id: nil), :count).by(4) + .and change(Vye::Verification.where(award_id: nil), :count).by(4) + .and change(Vye::Verification.where.not(user_info_id: nil), :count).by(-4) + .and change(Vye::Verification.where.not(award_id: nil), :count).by(-4) + + described_class.drain + # rubocop:enable RSpec/ChangeByZero + end - # direct deposit changes, addy changes and awards are deleted - # verifications have their foreign keys set to null - it 'deletes inactive BDNs and processes their ri children correctly' do - # create one active and one inactive BDN - create(:vye_bdn_clone_with_user_info_children) - create(:vye_bdn_clone_with_user_info_children, :active) - - # rubocop:disable RSpec/ChangeByZero - expect do - described_class.new.perform - end.to change(Vye::BdnClone, :count) - .by(-1) - .and change(Vye::AddressChange, :count).by(-3) # 1 addy's created initially - .and change(Vye::Award, :count).by(-4) - .and change(Vye::DirectDepositChange, :count).by(-2) - .and change(Vye::Verification, :count).by(0) - .and change(Vye::Verification.where(user_info_id: nil), :count).by(4) - .and change(Vye::Verification.where(award_id: nil), :count).by(4) - .and change(Vye::Verification.where.not(user_info_id: nil), :count).by(-4) - .and change(Vye::Verification.where.not(award_id: nil), :count).by(-4) - - described_class.drain - # rubocop:enable RSpec/ChangeByZero + it 'does not delete or nilify anything if there are no inactive BDNs' do + create(:vye_bdn_clone_with_user_info_children, :active) + + # rubocop:disable RSpec/ChangeByZero + expect do + described_class.new.perform + end.to change(Vye::BdnClone, :count) + .by(-0) + .and change(Vye::AddressChange, :count).by(0) + .and change(Vye::Award, :count).by(0) + .and change(Vye::DirectDepositChange, :count).by(0) + .and change(Vye::Verification, :count).by(0) + + described_class.drain + # rubocop:enable RSpec/ChangeByZero + end end - it 'does not delete or nilify anything if there are no inactive BDNs' do - create(:vye_bdn_clone_with_user_info_children, :active) - - # rubocop:disable RSpec/ChangeByZero - expect do - described_class.new.perform - end.to change(Vye::BdnClone, :count) - .by(-0) - .and change(Vye::AddressChange, :count).by(0) - .and change(Vye::Award, :count).by(0) - .and change(Vye::DirectDepositChange, :count).by(0) - .and change(Vye::Verification, :count).by(0) - - described_class.drain - # rubocop:enable RSpec/ChangeByZero + context 'when it is a holiday' do + before do + Timecop.freeze(Time.zone.local(2024, 7, 4)) # Independence Day + end + + after do + Timecop.return + end + + it 'does not process deactivated BDNs' do + expect(Vye::CloudTransfer).not_to receive(:delete_inactive_bdns) + + expect do + described_class.new.perform + end.not_to(change { Sidekiq::Worker.jobs.size }) + end end end diff --git a/modules/vye/spec/sidekiq/vye/sundown_sweep/delete_processed_s3_files_spec.rb b/modules/vye/spec/sidekiq/vye/sundown_sweep/delete_processed_s3_files_spec.rb index 47dfb42f519..76ef998656c 100644 --- a/modules/vye/spec/sidekiq/vye/sundown_sweep/delete_processed_s3_files_spec.rb +++ b/modules/vye/spec/sidekiq/vye/sundown_sweep/delete_processed_s3_files_spec.rb @@ -1,20 +1,49 @@ # frozen_string_literal: true require 'rails_helper' -require Vye::Engine.root / 'spec/rails_helper' +require 'timecop' describe Vye::SundownSweep::DeleteProcessedS3Files, type: :worker do before do Sidekiq::Job.clear_all end - it 'checks the existence of described_class' do - expect(Vye::CloudTransfer).to receive(:remove_aws_files_from_s3_buckets) + context 'when it is not a holiday' do + before do + Timecop.freeze(Time.zone.local(2024, 7, 2)) # Regular work day + end - expect do - described_class.perform_async - end.to change { Sidekiq::Worker.jobs.size }.by(1) + after do + Timecop.return + end - described_class.drain + it 'checks the existence of described_class' do + expect(Vye::CloudTransfer).to receive(:remove_aws_files_from_s3_buckets) + + expect do + described_class.perform_async + end.to change { Sidekiq::Worker.jobs.size }.by(1) + + described_class.drain + end + end + + context 'when it is a holiday' do + before do + Timecop.freeze(Time.zone.local(2024, 7, 4)) # Independence Day + end + + after do + Timecop.return + end + + it 'does not process S3 files' do + expect(Vye::CloudTransfer).not_to receive(:remove_aws_files_from_s3_buckets) + described_class.new.perform + + expect do + described_class.new.perform + end.not_to(change { Sidekiq::Worker.jobs.size }) + end end end diff --git a/modules/vye/spec/sidekiq/vye/sundown_sweep/purge_stale_verifications_spec.rb b/modules/vye/spec/sidekiq/vye/sundown_sweep/purge_stale_verifications_spec.rb deleted file mode 100644 index 431a1f64a60..00000000000 --- a/modules/vye/spec/sidekiq/vye/sundown_sweep/purge_stale_verifications_spec.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' -require Vye::Engine.root / 'spec/rails_helper' - -describe Vye::SundownSweep::PurgeStaleVerifications, type: :worker do - before do - Sidekiq::Job.clear_all - end - - it 'checks the existence of described_class' do - expect(described_class).to be_a(Class) - end -end diff --git a/modules/vye/spec/sidekiq/vye/sundown_sweep_spec.rb b/modules/vye/spec/sidekiq/vye/sundown_sweep_spec.rb index 29b30f54062..2742d44d326 100644 --- a/modules/vye/spec/sidekiq/vye/sundown_sweep_spec.rb +++ b/modules/vye/spec/sidekiq/vye/sundown_sweep_spec.rb @@ -17,7 +17,6 @@ expect(Vye::SundownSweep::ClearDeactivatedBdns).to have_enqueued_sidekiq_job expect(Vye::SundownSweep::DeleteProcessedS3Files).to have_enqueued_sidekiq_job - expect(Vye::SundownSweep::PurgeStaleVerifications).to have_enqueued_sidekiq_job end # Exceptions are tested one by one rather than all at once because it's easier to debug diff --git a/spec/controllers/v0/disability_compensation_forms_controller_spec.rb b/spec/controllers/v0/disability_compensation_forms_controller_spec.rb index 48c3a32163f..7472f584b5e 100644 --- a/spec/controllers/v0/disability_compensation_forms_controller_spec.rb +++ b/spec/controllers/v0/disability_compensation_forms_controller_spec.rb @@ -17,6 +17,7 @@ context 'evss' do before do Flipper.disable('disability_compensation_lighthouse_brd') + Flipper.disable('disability_compensation_staging_lighthouse_brd') end it 'returns separation locations' do @@ -39,6 +40,7 @@ context 'lighthouse' do before do Flipper.enable('disability_compensation_lighthouse_brd') + Flipper.disable('disability_compensation_staging_lighthouse_brd') end after(:all) do @@ -61,6 +63,34 @@ end end end + + context 'lighthouse staging' do + before do + Flipper.enable('disability_compensation_lighthouse_brd') + Flipper.enable('disability_compensation_staging_lighthouse_brd') + end + + after(:all) do + Flipper.disable('disability_compensation_lighthouse_brd') + Flipper.disable('disability_compensation_staging_lighthouse_brd') + end + + it 'returns separation locations' do + VCR.use_cassette('brd/separation_locations_staging') do + get(:separation_locations) + expect(JSON.parse(response.body)['separation_locations'].present?).to eq(true) + end + end + + it 'uses the cached response on the second request' do + VCR.use_cassette('brd/separation_locations_staging') do + 2.times do + get(:separation_locations) + expect(response.status).to eq(200) + end + end + end + end end describe '#rating_info' do diff --git a/spec/lib/disability_compensation/providers/brd/lighthouse_staging_brd_provider_spec.rb b/spec/lib/disability_compensation/providers/brd/lighthouse_staging_brd_provider_spec.rb new file mode 100644 index 00000000000..901f2aa0e5a --- /dev/null +++ b/spec/lib/disability_compensation/providers/brd/lighthouse_staging_brd_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/brd/lighthouse_staging_brd_provider' +require 'support/disability_compensation_form/shared_examples/brd_provider' +require 'lighthouse/service_exception' + +RSpec.describe LighthouseStagingBRDProvider do + let(:current_user) { build(:user, :loa3) } + + before do + @provider = LighthouseStagingBRDProvider.new(current_user) + Flipper.enable(ApiProviderFactory::FEATURE_TOGGLE_BRD) + Flipper.enable(:disability_compensation_staging_lighthouse_brd) + end + + after do + Flipper.disable(ApiProviderFactory::FEATURE_TOGGLE_BRD) + Flipper.disable(:disability_compensation_staging_lighthouse_brd) + end + + it_behaves_like 'brd provider' + + it 'retrieves separation locations from the Lighthouse API' do + VCR.use_cassette('brd/separation_locations_staging') do + response = @provider.get_separation_locations + expect(response.separation_locations.length).to eq(327) + end + end +end diff --git a/spec/requests/swagger_spec.rb b/spec/requests/swagger_spec.rb index ba6c3250da0..4b579630800 100644 --- a/spec/requests/swagger_spec.rb +++ b/spec/requests/swagger_spec.rb @@ -1098,6 +1098,7 @@ Flipper.disable('disability_compensation_prevent_submission_job') Flipper.disable(ApiProviderFactory::FEATURE_TOGGLE_BRD) Flipper.disable('disability_compensation_production_tester') + Flipper.disable(:disability_compensation_staging_lighthouse_brd) end let(:form526v2) do diff --git a/spec/support/vcr_cassettes/brd/separation_locations_staging.yml b/spec/support/vcr_cassettes/brd/separation_locations_staging.yml new file mode 100644 index 00000000000..4726f5d9a98 --- /dev/null +++ b/spec/support/vcr_cassettes/brd/separation_locations_staging.yml @@ -0,0 +1,35 @@ +--- +http_interactions: + - request: + method: get + uri: https://staging-api.va.gov/services/benefits-reference-data/v1/intake-sites + body: + encoding: US-ASCII + string: '' + headers: + Apikey: + - stagingapikeyhere + User-Agent: + - Faraday v0.17.6 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: '' + headers: + Date: + - Thu, 27 Apr 2023 20:39:40 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Strict-Transport-Security: + - max-age=16000000; includeSubDomains; preload; + body: + encoding: UTF-8 + string: "{\"totalItems\":327,\"totalPages\":1,\"links\":[{\"href\":\"https://staging-api.va.gov/services/benefits-reference-data/v1/intake-sites\",\"rel\":\"self\"}],\"items\":[{\"id\":24912,\"description\":\"AF Academy\"},{\"id\":26722,\"description\":\"ANG Hub\"},{\"id\":24911,\"description\":\"Aberdeen Proving Ground\"},{\"id\":26821,\"description\":\"Al Udeid, Qatar\"},{\"id\":26742,\"description\":\"Alameda Coast Guard\"},{\"id\":24913,\"description\":\"Albuquerque\"},{\"id\":24914,\"description\":\"Altus AFB*\"},{\"id\":24915,\"description\":\"Anchorage\"},{\"id\":26767,\"description\":\"Andersen AFB - Air Force / Guam\"},{\"id\":24916,\"description\":\"Andrews AFB\"},{\"id\":24917,\"description\":\"Annapolis NS/USNA\"},{\"id\":26749,\"description\":\"Ansbach, Germany\"},{\"id\":26781,\"description\":\"Arifjan, Kuwait\"},{\"id\":24918,\"description\":\"Atlanta\"},{\"id\":26774,\"description\":\"Aviano AB, Italy\"},{\"id\":26719,\"description\":\"Bahrain / Kingdom of Bahrain\"},{\"id\":24919,\"description\":\"Baltimore\"},{\"id\":26750,\"description\":\"Bamberg, Germany\"},{\"id\":24921,\"description\":\"Barksdale AFB\"},{\"id\":26732,\"description\":\"Baumholder, Germany\"},{\"id\":26741,\"description\":\"Beale AFB\"},{\"id\":27371,\"description\":\"Beaufort Naval Hospital\"},{\"id\":26718,\"description\":\"Benelux (Brussels/Schinnen/SHAPE)\"},{\"id\":24922,\"description\":\"Bethesada NH\"},{\"id\":24923,\"description\":\"Boise\"},{\"id\":24924,\"description\":\"Bolling AFB\"},{\"id\":24925,\"description\":\"Boston\"},{\"id\":26816,\"description\":\"Buchanan\"},{\"id\":24928,\"description\":\"Buckley AGB\"},{\"id\":24929,\"description\":\"Buffalo\"},{\"id\":26795,\"description\":\"CBC Gulfport\"},{\"id\":26771,\"description\":\"Camp Atterbury (Demob)\"},{\"id\":24930,\"description\":\"Camp Casey (Korea)\"},{\"id\":24931,\"description\":\"Camp Lejeune\"},{\"id\":24932,\"description\":\"Camp Pendleton*\"},{\"id\":26798,\"description\":\"Camp Shelby (Demob)\"},{\"id\":26808,\"description\":\"Cannon AFB\"},{\"id\":26803,\"description\":\"Cape May Coast Guard\"},{\"id\":24934,\"description\":\"Carlisle Barracks\"},{\"id\":2550306,\"description\":\"Castle AFB\"},{\"id\":24936,\"description\":\"Charleston AFB\"},{\"id\":27372,\"description\":\"Charleston Naval Health Clinic\"},{\"id\":24937,\"description\":\"Cherry Point MCAS\"},{\"id\":24938,\"description\":\"Cheyenne\"},{\"id\":24939,\"description\":\"Chicago\"},{\"id\":24940,\"description\":\"Cleveland\"},{\"id\":26746,\"description\":\"Coast Guard Sector Miami\"},{\"id\":24943,\"description\":\"Columbia\"},{\"id\":26796,\"description\":\"Columbus AFB\"},{\"id\":24944,\"description\":\"Corpus Christi NAS\"},{\"id\":24946,\"description\":\"Dahlgren NSWC\"},{\"id\":24947,\"description\":\"Davis-Monthan AFB\"},{\"id\":24948,\"description\":\"Denver\"},{\"id\":24950,\"description\":\"Des Moines\"},{\"id\":24951,\"description\":\"Detroit\"},{\"id\":26791,\"description\":\"Detroit Arsenal\"},{\"id\":24952,\"description\":\"DiLorenzo-Pentagon\"},{\"id\":26772,\"description\":\"Diego Garcia / BIOT\"},{\"id\":24953,\"description\":\"Dover AFB\"},{\"id\":26787,\"description\":\"Dugway Proving Grounds\"},{\"id\":24954,\"description\":\"Dyess AFB\"},{\"id\":24955,\"description\":\"Earle Naval Weapons (new)\"},{\"id\":26733,\"description\":\"Edwards AFB\"},{\"id\":24956,\"description\":\"Eglin AFB*\"},{\"id\":26711,\"description\":\"Eielson AFB\"},{\"id\":26827,\"description\":\"Ellsworth AFB\"},{\"id\":24957,\"description\":\"Elmendorf AFB\"},{\"id\":24958,\"description\":\"Everett NS\"},{\"id\":24959,\"description\":\"Fairchild AFB*\"},{\"id\":24960,\"description\":\"Fargo\"},{\"id\":2550247,\"description\":\"Fort Barfoot (formerly Fort Pickett)\"},{\"id\":24961,\"description\":\"Fort Belvoir\"},{\"id\":24963,\"description\":\"Fort Bliss\"},{\"id\":24965,\"description\":\"Fort Campbell\"},{\"id\":24966,\"description\":\"Fort Carson\"},{\"id\":24971,\"description\":\"Fort Cavazos (formerly Fort Hood)\"},{\"id\":24967,\"description\":\"Fort Dietrick\"},{\"id\":24968,\"description\":\"Fort Dix\"},{\"id\":24969,\"description\":\"Fort Drum\"},{\"id\":24970,\"description\":\"Fort Eisenhower (formerly Fort Gordon)\"},{\"id\":24976,\"description\":\"Fort Gregg-Adams (formerly Fort Lee)\"},{\"id\":24972,\"description\":\"Fort Irwin\"},{\"id\":24973,\"description\":\"Fort Jackson\"},{\"id\":24980,\"description\":\"Fort Johnson (formerly Fort Polk)\"},{\"id\":24974,\"description\":\"Fort Knox\"},{\"id\":24975,\"description\":\"Fort Leavenworth\"},{\"id\":24977,\"description\":\"Fort Lewis\"},{\"id\":24964,\"description\":\"Fort Liberty (formerly Fort Bragg)\"},{\"id\":24978,\"description\":\"Fort Meade\"},{\"id\":24962,\"description\":\"Fort Moore (formerly Fort Benning)\"},{\"id\":24983,\"description\":\"Fort Novosel (formerly Fort Rucker)\"},{\"id\":24981,\"description\":\"Fort Richardson\"},{\"id\":24982,\"description\":\"Fort Riley\"},{\"id\":24984,\"description\":\"Fort Sam Houston\"},{\"id\":24986,\"description\":\"Fort Sill\"},{\"id\":24987,\"description\":\"Fort Stewart\"},{\"id\":2550246,\"description\":\"Fort Walker (formerly Fort A.P. Hill)\"},{\"id\":26786,\"description\":\"Ft Devens\"},{\"id\":26804,\"description\":\"Ft Hamilton\"},{\"id\":26832,\"description\":\"Ft Worth (ANG Hub)\"},{\"id\":26830,\"description\":\"Ft. Bliss\"},{\"id\":24988,\"description\":\"Ft. Eustis\"},{\"id\":24989,\"description\":\"Ft. Harrison\"},{\"id\":24990,\"description\":\"Ft. Huachuca\"},{\"id\":24991,\"description\":\"Ft. Leonard Wood\"},{\"id\":26846,\"description\":\"Ft. McCoy\"},{\"id\":26714,\"description\":\"Ft. Wainwright\"},{\"id\":26752,\"description\":\"Geilenkirchen, Germany\"},{\"id\":24992,\"description\":\"Goodfellow AFB\"},{\"id\":26799,\"description\":\"Grand Forks AFB\"},{\"id\":24993,\"description\":\"Great Lakes NTC\"},{\"id\":26748,\"description\":\"Guantanamo Bay / Cuba\"},{\"id\":26784,\"description\":\"Hanscom AFB\"},{\"id\":24994,\"description\":\"Hartford\"},{\"id\":26725,\"description\":\"Heidelberg Germany\"},{\"id\":24995,\"description\":\"Hickham AFB\"},{\"id\":24996,\"description\":\"Hill AFB\"},{\"id\":26807,\"description\":\"Holloman AFB\"},{\"id\":24997,\"description\":\"Homeland Security (HS)\"},{\"id\":24998,\"description\":\"Honolulu\"},{\"id\":24999,\"description\":\"Houston\"},{\"id\":26758,\"description\":\"Hunter AAF\"},{\"id\":25000,\"description\":\"Huntington\"},{\"id\":25001,\"description\":\"Hurlburt Field\"},{\"id\":26829,\"description\":\"Incirlik AB, Turkey\"},{\"id\":25003,\"description\":\"Indianapolis\"},{\"id\":26823,\"description\":\"JB Charleston\"},{\"id\":26824,\"description\":\"JB Charleston-AB\"},{\"id\":26838,\"description\":\"JB Langley-Eustis\"},{\"id\":26843,\"description\":\"JB Lewis-McChord\"},{\"id\":26769,\"description\":\"JB Pearl Harbor-Hickam\"},{\"id\":26840,\"description\":\"JBMH-Henderson Hall\"},{\"id\":26839,\"description\":\"JBMH-Myer\"},{\"id\":25004,\"description\":\"Jackson\"},{\"id\":25005,\"description\":\"Jacksonville NAS\"},{\"id\":26805,\"description\":\"Joint Base McGuire-Dix (Demob)\"},{\"id\":26724,\"description\":\"Kaiserslautern Germany\"},{\"id\":25006,\"description\":\"Keesler AFB\"},{\"id\":25007,\"description\":\"Key West CG\"},{\"id\":25008,\"description\":\"Key West NAS\"},{\"id\":25009,\"description\":\"Kings Bay NAS\"},{\"id\":25010,\"description\":\"Kirtland AFB\"},{\"id\":25011,\"description\":\"Kitsap NS* (formerly Bremerton NB)\"},{\"id\":26779,\"description\":\"Kunsan AB, Korea\"},{\"id\":25012,\"description\":\"Lackland AFB\"},{\"id\":26820,\"description\":\"Lajes Field\"},{\"id\":25013,\"description\":\"Lakehurst (Southern NJ)\"},{\"id\":26735,\"description\":\"Landstuhl Reg Med Ctr\"},{\"id\":25231,\"description\":\"Langley Air Force Base\"},{\"id\":26834,\"description\":\"Laughlin AFB\"},{\"id\":25016,\"description\":\"Lemoore NAS\"},{\"id\":25017,\"description\":\"Lincoln\"},{\"id\":25019,\"description\":\"Little Rock\"},{\"id\":25020,\"description\":\"Little Rock AFB\"},{\"id\":25021,\"description\":\"Los Angeles\"},{\"id\":26727,\"description\":\"Los Angeles AFB\"},{\"id\":25022,\"description\":\"Louisville\"},{\"id\":26723,\"description\":\"Luke AFB\"},{\"id\":25029,\"description\":\"MC Recruit Depot\"},{\"id\":26738,\"description\":\"MCAGTC 29 Palms\"},{\"id\":26826,\"description\":\"MCAS Beaufort\"},{\"id\":26720,\"description\":\"MCAS Yuma\"},{\"id\":25030,\"description\":\"MCB Hawaii\"},{\"id\":26842,\"description\":\"MCB Quantico\"},{\"id\":26760,\"description\":\"MCLB Albany\"},{\"id\":26729,\"description\":\"MCRD San Diego\"},{\"id\":25023,\"description\":\"MacDill AFB\"},{\"id\":25024,\"description\":\"Malmstrom AFB\"},{\"id\":25025,\"description\":\"Manchester\"},{\"id\":25026,\"description\":\"Manila\"},{\"id\":25027,\"description\":\"Maxwell AFB\"},{\"id\":25028,\"description\":\"Mayport NS*\"},{\"id\":25031,\"description\":\"McChord AFB\"},{\"id\":25032,\"description\":\"McConnell AFB\"},{\"id\":25033,\"description\":\"McGuire AFB\"},{\"id\":26757,\"description\":\"Miami\"},{\"id\":25034,\"description\":\"Miami CG\"},{\"id\":25035,\"description\":\"Milwaukee\"},{\"id\":26800,\"description\":\"Minot AFB\"},{\"id\":25036,\"description\":\"Miramar MCAS\"},{\"id\":25037,\"description\":\"Montgomery\"},{\"id\":26755,\"description\":\"Moody AFB\"},{\"id\":25038,\"description\":\"Mountain Home AFB\"},{\"id\":25039,\"description\":\"Muskogee\"},{\"id\":26811,\"description\":\"NAS Fallon / NV\"},{\"id\":26833,\"description\":\"NAS JRB Fort Worth\"},{\"id\":26782,\"description\":\"NAS JRB New Orleans\"},{\"id\":26835,\"description\":\"NAS Kingsville\"},{\"id\":26797,\"description\":\"NAS Meridian\"},{\"id\":26766,\"description\":\"NAVBASE Guam Barrigada / Guam\"},{\"id\":26736,\"description\":\"NAWS China Lake\"},{\"id\":26721,\"description\":\"NB San Diego\"},{\"id\":26822,\"description\":\"NS Newport\"},{\"id\":25049,\"description\":\"NSA\"},{\"id\":26828,\"description\":\"NSA Midsouth Memphis\"},{\"id\":26765,\"description\":\"NSA Souda Bay / Greece\"},{\"id\":26801,\"description\":\"NSY Portsmouth\"},{\"id\":26775,\"description\":\"Naples / ITALY\"},{\"id\":25041,\"description\":\"Nashville\"},{\"id\":26871,\"description\":\"National Capital Region (NCR)\"},{\"id\":26802,\"description\":\"Naval Base Ventura County\"},{\"id\":26810,\"description\":\"Nellis AFB, NV/Creech AFB, NV\"},{\"id\":25043,\"description\":\"New London NSB\"},{\"id\":25044,\"description\":\"New Orleans\"},{\"id\":25045,\"description\":\"New River MCAS\"},{\"id\":25046,\"description\":\"New York\"},{\"id\":25047,\"description\":\"Newark\"},{\"id\":25048,\"description\":\"Norfolk NB\"},{\"id\":26831,\"description\":\"North Ft. Hood\"},{\"id\":25051,\"description\":\"Oakland\"},{\"id\":25053,\"description\":\"Offutt AFB\"},{\"id\":26780,\"description\":\"Osan AB, Korea\"},{\"id\":26849,\"description\":\"Other - AGR\"},{\"id\":26850,\"description\":\"Other - Not on List\"},{\"id\":25054,\"description\":\"Patrick AFB\"},{\"id\":25055,\"description\":\"Patuxnet River NAS\"},{\"id\":25056,\"description\":\"Pearl Harbor NB\"},{\"id\":25057,\"description\":\"Pensacola NAS\"},{\"id\":25058,\"description\":\"Pensacola NH\"},{\"id\":27373,\"description\":\"Pentagon In-Take Site\"},{\"id\":26743,\"description\":\"Petaluma Coast Guard\"},{\"id\":25059,\"description\":\"Peterson AFB\"},{\"id\":25060,\"description\":\"Philadelphia\"},{\"id\":25061,\"description\":\"Phoenix\"},{\"id\":25062,\"description\":\"Pittsburgh\"},{\"id\":25064,\"description\":\"Portland\"},{\"id\":25065,\"description\":\"Portsmouth NAS\"},{\"id\":25066,\"description\":\"Portsmouth NH\"},{\"id\":26740,\"description\":\"Presidio of Monterey\"},{\"id\":25067,\"description\":\"Providence\"},{\"id\":26762,\"description\":\"RAF Alconbury, UK\"},{\"id\":26761,\"description\":\"RAF Lakenheath, UK\"},{\"id\":26763,\"description\":\"RAF Menwith Hill, UK\"},{\"id\":26764,\"description\":\"RAF Mildenhall, UK\"},{\"id\":26753,\"description\":\"Ramstein AB, Germany\"},{\"id\":25068,\"description\":\"Randolph AFB\"},{\"id\":25069,\"description\":\"Redstone Arsenal\"},{\"id\":25070,\"description\":\"Reno\"},{\"id\":25071,\"description\":\"Roanoke\"},{\"id\":26759,\"description\":\"Robins AFB\"},{\"id\":26770,\"description\":\"Rock Island\"},{\"id\":26737,\"description\":\"Rota / SPAIN\"},{\"id\":25072,\"description\":\"Salt Lake City\"},{\"id\":25073,\"description\":\"San Diego\"},{\"id\":25079,\"description\":\"San Francisco\"},{\"id\":25080,\"description\":\"San Juan\"},{\"id\":25075,\"description\":\"Schofield Barracks\"},{\"id\":25076,\"description\":\"Schriever AFB\"},{\"id\":26734,\"description\":\"Schweinfurt Germany\"},{\"id\":25077,\"description\":\"Scotia ND(now Saratoga Springs Transition Station)\"},{\"id\":25078,\"description\":\"Scott AFB\"},{\"id\":25081,\"description\":\"Seattle\"},{\"id\":25083,\"description\":\"Seymour Johnson AFB\"},{\"id\":25084,\"description\":\"Shaw AFB\"},{\"id\":25085,\"description\":\"Sheppard AFB\"},{\"id\":26776,\"description\":\"Sigonella / ITALY\"},{\"id\":25086,\"description\":\"Sioux Falls\"},{\"id\":26751,\"description\":\"Spangdahlem AB, Germany\"},{\"id\":25088,\"description\":\"St. Louis\"},{\"id\":25089,\"description\":\"St. Paul\"},{\"id\":25090,\"description\":\"St. Petersburg\"},{\"id\":26731,\"description\":\"Stuttgart Germany\"},{\"id\":26837,\"description\":\"Texas City Coast Guard\"},{\"id\":25092,\"description\":\"Tinker AFB\"},{\"id\":25093,\"description\":\"Togus\"},{\"id\":25094,\"description\":\"Travis AFB\"},{\"id\":25095,\"description\":\"Tripler AMC\"},{\"id\":25096,\"description\":\"Tyndall AFB\"},{\"id\":26817,\"description\":\"USCG Air Station Ramey\"},{\"id\":26806,\"description\":\"USCG Atlantic City\"},{\"id\":26788,\"description\":\"USCG Baltimore\"},{\"id\":26728,\"description\":\"USCG Base Los Angeles\"},{\"id\":26819,\"description\":\"USCG Borinquen\"},{\"id\":26785,\"description\":\"USCG Boston\"},{\"id\":26812,\"description\":\"USCG Buffalo\"},{\"id\":26825,\"description\":\"USCG Charleston\"},{\"id\":26744,\"description\":\"USCG Clearwater\"},{\"id\":26814,\"description\":\"USCG Cleveland\"},{\"id\":26836,\"description\":\"USCG Corpus Christi\"},{\"id\":26790,\"description\":\"USCG Detroit\"},{\"id\":26792,\"description\":\"USCG Grand Haven\"},{\"id\":26768,\"description\":\"USCG Honolulu\"},{\"id\":26745,\"description\":\"USCG Humboldt Bay\"},{\"id\":26756,\"description\":\"USCG Jacksonville\"},{\"id\":26709,\"description\":\"USCG Juneau\"},{\"id\":26710,\"description\":\"USCG Ketchikan\"},{\"id\":26712,\"description\":\"USCG Kodiak\"},{\"id\":26813,\"description\":\"USCG Long Island\"},{\"id\":26848,\"description\":\"USCG Martinsburg\"},{\"id\":26847,\"description\":\"USCG Milwaukee\"},{\"id\":26715,\"description\":\"USCG Mobile\"},{\"id\":26783,\"description\":\"USCG New Orleans\"},{\"id\":26815,\"description\":\"USCG Portland\"},{\"id\":26845,\"description\":\"USCG Portsmouth\"},{\"id\":26841,\"description\":\"USCG Recruiting Command\"},{\"id\":26789,\"description\":\"USCG SW Harbor\"},{\"id\":26730,\"description\":\"USCG San Diego\"},{\"id\":26747,\"description\":\"USCG Savannah\"},{\"id\":26844,\"description\":\"USCG Seattle\"},{\"id\":26713,\"description\":\"USCG Sitka\"},{\"id\":26794,\"description\":\"USCG St Louis\"},{\"id\":26818,\"description\":\"USCG Station San Juan\"},{\"id\":26793,\"description\":\"USCG Su Ste Marie\"},{\"id\":26716,\"description\":\"USCG Valdez\"},{\"id\":27374,\"description\":\"VAMC Biloxi\"},{\"id\":25097,\"description\":\"Vance AFB\"},{\"id\":26739,\"description\":\"Vandenberg AFB\"},{\"id\":26773,\"description\":\"Vicenza Italy\"},{\"id\":26726,\"description\":\"Vilseck Germany\"},{\"id\":25098,\"description\":\"Waco\"},{\"id\":25099,\"description\":\"Walter Reed AMC - C&P\"},{\"id\":25100,\"description\":\"Warren AFB (Cheyenne)\"},{\"id\":25102,\"description\":\"West Point\"},{\"id\":25103,\"description\":\"Whidbey Island NAS\"},{\"id\":25105,\"description\":\"White River Junction\"},{\"id\":26809,\"description\":\"White Sands\"},{\"id\":25104,\"description\":\"Whiteman AFB\"},{\"id\":25106,\"description\":\"Whiting Field\"},{\"id\":25107,\"description\":\"Wichita\"},{\"id\":26754,\"description\":\"Wiesbaden Germany\"},{\"id\":25109,\"description\":\"Wilmington\"},{\"id\":25110,\"description\":\"Winston-Salem\"},{\"id\":25111,\"description\":\"Wright-Patterson AFB\"},{\"id\":26777,\"description\":\"Yokosuka / JAPAN\"},{\"id\":26778,\"description\":\"Yokota AB, Japan\"},{\"id\":25112,\"description\":\"Yongsan (Korea)\"},{\"id\":26717,\"description\":\"Yuma Proving Grounds\"}]}" + recorded_at: Thu, 27 Apr 2023 20:39:40 GMT +recorded_with: VCR 6.1.0