diff --git a/.gitignore b/.gitignore index 18884fd4e16..b655037991d 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ client/junit.xml !/reports/sql_queries !/reports/.keep credstash.log +client/mocks/webex-mocks/webex-mock.json # Ignore MS Office temp files ~$* diff --git a/MAC_M1.md b/MAC_M1.md index 603c98e0b71..6600e77dc99 100644 --- a/MAC_M1.md +++ b/MAC_M1.md @@ -118,7 +118,7 @@ OpenSSL --- 1. Download openssl@1.1 and openssl@3 from this [link](https://boozallen.sharepoint.com/teams/VABID/appeals/Documents/Forms/AllItems.aspx?id=%2Fteams%2FVABID%2Fappeals%2FDocuments%2FDevelopment%2FDeveloper%20Setup%20Resources%2FM1%20Mac%20Developer%20Setup&viewid=8a8eaf3e%2D2c12%2D4c87%2Db95f%2D4eab3428febd) 2. Open “Finder” and find the two folders under “Downloads” -3. Extract the `.tar.gz` files +3. Extract the `.tar.gz` or `.zip` archives 4. In each of the extracted folders: 1. Navigate to the `/usr/local/homebrew/Cellar` subfolder 2. Copy the openssl folder to your local machine's `/usr/local/homebrew/Cellar` folder @@ -168,22 +168,26 @@ Run dev setup scripts in Caseflow repo 1. Open a **Rosetta** terminal and navigate to /usr/local, run the command ```sudo spctl --global-disable``` 2. In the **Rosetta** terminal, install pyenv and the required python2 version: 1. `brew install pyenv` - 2. `pyenv install 2.7.18` - 3. In the caseflow directory, run `pyenv local 2.7.18` to set the version + 2. `pyenv rehash` + 3. `pyenv install 2.7.18` + 4. In the caseflow directory, run `pyenv local 2.7.18` to set the version 3. In the **Rosetta** terminal navigate to caseflow folder: - 1. set ```RUBY_CONFIGURE_OPTS="--with-openssl-dir=/usr/local/homebrew/Cellar/openssl@1.1"``` - 2. run `rbenv install 2.7.3` - 3. run `gem install pg:1.1.4 -- --with-pg-config=/Applications/Postgres.app/Contents/Versions/latest/bin/pg_config` - 4. Install v8@3.15 by doing the following (these steps assume that vi/vim is the default editor): + 1. set ```export RUBY_CONFIGURE_OPTS="--with-openssl-dir=/usr/local/homebrew/Cellar/openssl@1.1"``` + 2. run `rbenv install $(cat .ruby-version)` + 3. run `rbenv rehash` + 4. run `gem install bundler -v $(grep -A 1 "BUNDLED WITH" Gemfile.lock | tail -n 1)` + 5. run `gem install pg:1.1.4 -- --with-pg-config=/Applications/Postgres.app/Contents/Versions/latest/bin/pg_config` + 6. Install v8@3.15 by doing the following (these steps assume that vi/vim is the default editor): 1. run `brew edit v8@3.15` 2. go to line 21 in the editor by typing `:21` Note: the line being removed is `disable! date: "2023-06-19", because: "depends on Python 2 to build"` 3. delete the line by pressing `d` twice 4. save and quit by typing `:x` - 5. Configure build opts for gem `therubyracer`: + 5. run `HOMEBREW_NO_INSTALL_FROM_API=1 brew install v8@3.15` + 7. Configure build opts for gem `therubyracer`: 1. `bundle config build.libv8 --with-system-v8` 2. `bundle config build.therubyracer --with-v8-dir=$(brew --prefix v8@3.15)` - 6. run ```./scripts/dev_env_setup_step2.sh``` + 8. run ```./scripts/dev_env_setup_step2.sh``` If you get a permission error while running gem install or bundle install, **do not run using sudo.** Set the permissions back to you for every directory under /.rbenv * Enter command: `sudo chown -R /Users//.rbenv` diff --git a/app/models/end_product_establishment.rb b/app/models/end_product_establishment.rb index bab3e8c5cdf..f8e0c1730f6 100644 --- a/app/models/end_product_establishment.rb +++ b/app/models/end_product_establishment.rb @@ -210,10 +210,7 @@ def sync! contentions unless result.status_type_code == EndProduct::STATUSES.key("Canceled") transaction do - update!( - synced_status: result.status_type_code, - last_synced_at: Time.zone.now - ) + update!(synced_status: result.status_type_code) status_cancelled? ? handle_cancelled_ep! : sync_source! close_request_issues_with_no_decision! end @@ -224,6 +221,11 @@ def sync! rescue StandardError => error Raven.extra_context(end_product_establishment_id: id) raise error + ensure + # Always update last_synced_at to ensure that SyncReviewsJob does not immediately re-enqueue + # End Product Establishments that fail to sync with BGS into the EndProductSyncJob. + # This will allow for other End Product Establishments to sync first before re-attempting. + update!(last_synced_at: Time.zone.now) end def fetch_dispositions_from_vbms diff --git a/client/app/queue/OrganizationUsers.jsx b/client/app/queue/OrganizationUsers.jsx index 8e66a73063e..1453040d758 100644 --- a/client/app/queue/OrganizationUsers.jsx +++ b/client/app/queue/OrganizationUsers.jsx @@ -279,23 +279,11 @@ export default class OrganizationUsers extends React.PureComponent { organization={this.props.organization} user={user} /> - {this.state.organizationName === 'Hearing Admin' && - !conferenceSelectionVisibility && ( -
- -
- )} - - )} - - - - ); + } + } + + + ; }); return diff --git a/client/mocks/webex-mocks/README.md b/client/mocks/webex-mocks/README.md new file mode 100644 index 00000000000..cca7ce34640 --- /dev/null +++ b/client/mocks/webex-mocks/README.md @@ -0,0 +1,59 @@ +Setup json server + +Step 1: Open a terminal + +Step 2: Navigate to the caseflow/client + +step 3: Run command: [npm install json-server] or [yarn add json-server] + +If the [npm install json-server] or [yarn add json-server] returns an error that resembles: + +error standard@17.1.0: The engine "node" is incompatible with this module. Expected version "^12.22.0 || ^14.17.0 || >=16.0.0". Got "15.1.0" + +extra steps may need to be taken. + +for brevity These instructions will follow the happy path. While in the client directory in terminal: +[nodenv install 14.21.2] +[nodenv local 14.21.2] + +If for any reason you want to go back to the original nodenv that was used prior to this change you can run, [nodenv local 12.13.0] + +If it all succeeds you can attempt the [npm install json-server] or [yarn add json-server] once again. + +This time with no issue. +given that the install goes as expected you can continue following the rest of the directions. + +If there are still issues in getting this to operate as expected, See your tech lead for asssisstance. + +step 4: Make sure casfelow application is running + +step 5: Autogenerate test data, run this command: npm run generate-webex(This will also create the json file) + +step 6: Run command: npm run webex-server + +\*info: You will recieve all available routes within the terminal under 'Resources' + +\*info: port must be set on a different port to run due to caseflow running on port 3000 + +step 7: Open a browser window in chrome and navigate to localhost:3050 [You will get the default page] + +\*info: You can use any api endpoint software you want like Postman, but a good lightweight vs code ext. is [Thunder Client] + +\*info: reference guides +[https://github.com/typicode/json-server/blob/master/README.md] + +Tutorial Resources: +[https://www.youtube.com/watch?v=_1kNqAybxW0&list=PLC3y8-rFHvwhc9YZIdqNL5sWeTCGxF4ya&index=1] + +To create a meeting the request body must have all of the keys and hit this endpoint? +[http://localhost:3050/fake.api-usgov.webex.com/v1/meetings] + +Get all conferencelinks with this endpoint +[http://localhost:3050/api/v1/conference-links] + +Javascript API call Fetch/Axios examples +[https://jsonplaceholder.typicode.com/] + + + + diff --git a/client/mocks/webex-mocks/meetingData.js b/client/mocks/webex-mocks/meetingData.js new file mode 100644 index 00000000000..0ed5772a76e --- /dev/null +++ b/client/mocks/webex-mocks/meetingData.js @@ -0,0 +1,37 @@ +const faker = require('faker'); + +const generateMeetingData = (response) => { + + return { + id: faker.random.uuid(), + jwt: { + sub: response.jwt.sub, + Nbf: response.jwt.Nbf, + Exp: response.jwt.Exp, + flow: { + id: faker.random.uuid(), + data: [ + { + uri: `${faker.internet.userName()}@intadmin.room.wbx2.com`, + }, + { + uri: `${faker.internet.userName()}@intadmin.room.wbx2.com`, + }, + ], + }, + }, + aud: faker.random.uuid(), + numGuest: faker.random.number({ min: 1, max: 10 }), + numHost: 1, + provideShortUrls: faker.random.boolean(), + verticalType: faker.company.catchPhrase(), + loginUrlForHost: faker.random.boolean(), + jweAlg: 'PBES2-HS512+A256KW', + saltLength: faker.random.number({ min: 1, max: 16 }), + iterations: faker.random.number({ min: 500, max: 2000 }), + enc: 'A256GCM', + jwsAlg: 'HS512', + }; +}; + +module.exports = generateMeetingData; diff --git a/client/mocks/webex-mocks/routes.json b/client/mocks/webex-mocks/routes.json new file mode 100644 index 00000000000..76516817fb9 --- /dev/null +++ b/client/mocks/webex-mocks/routes.json @@ -0,0 +1,5 @@ + +{ + "/api/v1/conference-links": "/conferenceLinks", + "/api/v1/conference-links/:id": "/conferenceLinks/:id" + } diff --git a/client/mocks/webex-mocks/webex-mock-generator.js b/client/mocks/webex-mocks/webex-mock-generator.js new file mode 100644 index 00000000000..6f8a108f5ee --- /dev/null +++ b/client/mocks/webex-mocks/webex-mock-generator.js @@ -0,0 +1,50 @@ +const fs = require('fs'); +const faker = require('faker'); +const generateMeetingData = require('./meetingData.js'); + +const generateConferenceLinks = () => { + let webexLinks = []; + + for (let id = 1; id <= 10; id++) { + const startDate = new Date('2021-01-01T00:00:00Z'); + const endDate = new Date('2023-01-01T00:00:00Z'); + + const randomStartDate = faker.date.between(startDate, endDate); + const randomEndDate = new Date(randomStartDate.getTime()); + + randomEndDate.setHours(randomEndDate.getHours() + 1); + + let startTime = randomStartDate.toISOString().replace('Z', ''); + let endTime = randomEndDate.toISOString().replace('Z', ''); + + let subject = faker.lorem.words(); + + let updatedValues = { + jwt: { + sub: subject, + Nbf: startTime, + Exp: endTime + } + }; + + webexLinks.push(generateMeetingData(updatedValues)); + } + + return webexLinks; +}; + +// Generate the data +const data = { + conferenceLinks: generateConferenceLinks(), + // ... other data models +}; + +// Check if the script is being run directly +if (require.main === module) { + fs.writeFileSync( + 'mocks/webex-mocks/webex-mock.json', + JSON.stringify(data, null, 2) + ); + // eslint-disable-next-line no-console + console.log("Generated new data in webex-mock.json"); +} diff --git a/client/mocks/webex-mocks/webex-mock-server.js b/client/mocks/webex-mocks/webex-mock-server.js new file mode 100644 index 00000000000..884bf9b2810 --- /dev/null +++ b/client/mocks/webex-mocks/webex-mock-server.js @@ -0,0 +1,231 @@ +const jsonServer = require('json-server'); +const server = jsonServer.create(); +const path = require('path'); +const router = jsonServer.router( + path.join('mocks/webex-mocks/webex-mock.json') +); +const generateMeetingData = require('./meetingData.js'); + +const middlewares = jsonServer.defaults(); +const routesRewrite = require('./routes.json'); + +server.use(middlewares); +server.use(jsonServer.bodyParser); + +// Apply the routes rewrites +server.use(jsonServer.rewriter(routesRewrite)); + +// Custom error routes and handlers +server.get('/error-400', (req, res) => { + res.status(400).json({ + message: 'The request was invalid or cannot be otherwise served.', + }); +}); + +server.get('/error-401', (req, res) => { + res.status(401).json({ + message: 'Authentication credentials were missing or incorrect.', + }); +}); + +server.get('/error-403', (req, res) => { + res.status(403).json({ + message: + 'The request is understood, but it has been refused or access is not allowed', + }); +}); + +server.get('/error-405', (req, res) => { + res.status(405).json({ + message: + 'The request was made to a resource using an HTTP request method that is not supported.', + }); +}); + +server.get('/error-409', (req, res) => { + res.status(409).json({ + message: + 'The request could not be processed because it conflicts with some established rule of the system.', + }); +}); + +server.get('/error-410', (req, res) => { + res.status(410).json({ + message: 'The requested resource is no longer available.', + }); +}); + +server.get('/error-415', (req, res) => { + res.status(415).json({ + message: + 'The request was made to a resource without specifying a media type or used a media type that is not supported.', + }); +}); + +server.get('/error-423', (req, res) => { + res.status(423).json({ + message: 'The requested resource is temporarily unavailable', + }); +}); + +server.get('/error-428', (req, res) => { + res.status(428).json({ + message: + 'File(s) cannot be scanned for malware and need to be force downloaded.', + }); +}); + +server.get('/error-429', (req, res) => { + res.status(429).json({ + message: + 'Too many requests have been sent in a given amount of time and the request has been rate limited.', + }); +}); + +server.get('/error-500', (req, res) => { + res.status(500).json({ + message: 'Something went wrong on the server.', + }); +}); + +server.get('/error-502', (req, res) => { + res.status(502).json({ + message: + 'The server received an invalid response from an upstream server while processing the request.', + }); +}); + +server.get('/error-503', (req, res) => { + res.status(503).json({ + message: 'Server is overloaded with requests. Try again later.', + }); +}); + +server.get('/error-504', (req, res) => { + res.status(504).json({ + message: + 'An upstream server failed to respond on time. If your query uses max parameter, please try to reduce it.', + }); +}); + +server.get('/health-check-yellow', (req, res) => { + res.status(200).json({ + status: 'yellow', + }); +}); + +server.get('/health-check-red', (req, res) => { + res.status(200).json({ + status: 'red', + }); +}); + +server.get('/health-check-green', (req, res) => { + res.status(200).json({ + status: 'green', + }); +}); + +const requiredKeys = [ + 'jwt', + 'aud', + 'numGuest', + 'numHost', + 'provideShortUrls', + 'verticalType', + 'loginUrlForHost', + 'jweAlg', + 'saltLength', + 'iterations', + 'enc', + 'jwsAlg' +]; + +server.post('/fake.api-usgov.webex.com/v1/meetings', (req, res) => { + const requestBody = req.body; + + // Check if all required keys are present + const missingKeys = requiredKeys.filter((key) => !(key in requestBody)); + + if (missingKeys.length > 0) { + res.status(400).json({ message: 'Missing required keys', missingKeys }); + } else if (!requestBody.jwt.sub || !requestBody.jwt.Nbf || !requestBody.jwt.Exp) { + res.status(400).json({ + message: 'Missing required params', + }); + } else { + + const db = router.db; + const conferenceLinks = db.get('conferenceLinks'); + + // Add generateMeetingData object to conferenceLinks + conferenceLinks.push(generateMeetingData(requestBody)).write(); + + res.status(200).json(generateMeetingData(requestBody)); + } +}); + +server.use(router); + +const errorRoutes = [ + '/error-400', + '/error-401', + '/error-403', + '/error-404', + '/error-405', + '/error-409', + '/error-410', + '/error-415', + '/error-423', + '/error-428', + '/error-429', + '/error-500', + '/error-502', + '/error-503', + '/error-504', + '/health-check-yellow', + '/health-check-red', + '/health-check-green', +]; + +server.listen(3050, () => { + /* eslint-disable no-console */ + console.log(' \\{^_^}/ hi!\n'); + console.log(' Loading mocks/webex-mocks/webex-mock.json'); + console.log(' Done\n'); + + console.log(' Resources:'); + + // Original routes from the database state + const originalRoutes = Object.keys(router.db.getState()); + + // Rewritten routes based on the routes.json rewrites + const rewrittenRoutes = originalRoutes.map((route) => { + for (let key in routesRewrite) { + if (routesRewrite[key] === `/${route}`) { + // returning the custom path + return key; + } + } + + return `/${route}`; + }); + + rewrittenRoutes.forEach((route) => { + console.log(` http://localhost:3050${route}`); + }); + + console.log('\n Error Routes:'); + errorRoutes.forEach((route) => { + console.log(` ${route}`); + }); + + console.log('\n Home'); + console.log(' http://localhost:3050'); + + console.log( + '\n Type s + enter at any time to create a snapshot of the database' + ); + console.log('Watching...'); + /* eslint-enable no-console */ +}); diff --git a/client/package.json b/client/package.json index cc2ba8fbd77..2c59c1ca2a0 100644 --- a/client/package.json +++ b/client/package.json @@ -25,7 +25,9 @@ "dev": "yarn run dev:clear-build && NODE_ENV=development yarn run build -w", "dev:hot": "webpack-dev-server", "storybook": "start-storybook -p 6006", - "build:storybook": "build-storybook -o ../public/storybook" + "build:storybook": "build-storybook -o ../public/storybook", + "webex-server": "node mocks/webex-mocks/webex-mock-server", + "generate-webex": "node mocks/webex-mocks/webex-mock-generator.js" }, "cacheDirectories": [ "node_modules", diff --git a/client/test/app/intake/components/CancelIntakeModal.test.js b/client/test/app/intake/components/CancelIntakeModal.test.js new file mode 100644 index 00000000000..4888a381d7e --- /dev/null +++ b/client/test/app/intake/components/CancelIntakeModal.test.js @@ -0,0 +1,100 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { Provider } from 'react-redux'; +import userEvent from '@testing-library/user-event'; +import { createStore, applyMiddleware } from 'redux'; +import thunk from 'redux-thunk'; +import rootReducer from 'app/queue/reducers'; +import CancelIntakeModal from 'app/intake/components/CancelIntakeModal'; +import { CANCELLATION_REASONS } from 'app/intake/constants'; + +jest.mock('redux', () => ({ + ...jest.requireActual('redux'), + bindActionCreators: () => jest.fn().mockImplementation(() => Promise.resolve(true)), +})); + +describe('CancelIntakeModal', () => { + const defaultProps = { + closeHandler: () => {}, + intakeId: '123 change?', + clearClaimant: jest.fn().mockImplementation(() => Promise.resolve(true)), + clearPoa: jest.fn().mockImplementation(() => Promise.resolve(true)), + submitCancel: jest.fn().mockImplementation(() => Promise.resolve(true)) + }; + const buttonText = 'Cancel intake'; + + afterEach(() => { + jest.clearAllMocks(); + }); + + const store = createStore(rootReducer, applyMiddleware(thunk)); + + const setup = (props) => + render( + + + + ); + + it('renders correctly', () => { + const modal = setup(defaultProps); + + expect(modal).toMatchSnapshot(); + }); + + it('displays cancellation options', () => { + const modal = setup(defaultProps); + + Object.values(CANCELLATION_REASONS).map((reason) => ( + expect(modal.getByText(reason.name)).toBeInTheDocument() + )); + }); + + it('should show other reason input when other is selected', async () => { + const modal = setup(defaultProps); + + await userEvent.click(screen.getByText('Other')); + + expect(modal.getByText('Tell us more about your situation.')).toBeInTheDocument(); + }); + + describe('cancel button', () => { + it('is disabled until "Other" is selected and the text input is filled out', async () => { + const modal = setup(defaultProps); + + expect(modal.getByText(buttonText)).toBeDisabled(); + + await userEvent.click(modal.getByText('Other')); + + expect(modal.getByText('Tell us more about your situation.')).toBeInTheDocument(); + expect(modal.getByText(buttonText)).toBeDisabled(); + + await userEvent.type(modal.getByRole('textbox'), 'Test'); + + expect(modal.getByText(buttonText)).not.toBeDisabled(); + }); + + it('is disabled until value (that is not "Other") is selected', async () => { + const modal = setup(defaultProps); + + expect(modal.getByText(buttonText)).toBeDisabled(); + + await userEvent.click(modal.getByText('System error')); + expect(modal.getByText(buttonText)).not.toBeDisabled(); + }); + }); + + it('should call the appropiate functions when Cancel intake is clicked', async () => { + const modal = setup(defaultProps); + + await userEvent.click(modal.getByText('System error')); + expect(modal.getByText(buttonText)).not.toBeDisabled(); + + await userEvent.click(modal.getByText('Cancel intake')); + expect(defaultProps.clearClaimant).toHaveBeenCalled(); + expect(defaultProps.clearPoa).toHaveBeenCalled(); + expect(defaultProps.submitCancel).toHaveBeenCalled(); + }); +}); diff --git a/client/test/app/intake/components/__snapshots__/CancelIntakeModal.test.js.snap b/client/test/app/intake/components/__snapshots__/CancelIntakeModal.test.js.snap new file mode 100644 index 00000000000..899aed3921f --- /dev/null +++ b/client/test/app/intake/components/__snapshots__/CancelIntakeModal.test.js.snap @@ -0,0 +1,440 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`CancelIntakeModal renders correctly 1`] = ` +Object { + "asFragment": [Function], + "baseElement": +
+
+ + ; +
+
+ , + "container":
+
+ + ; +
+
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; diff --git a/spec/feature/login_spec.rb b/spec/feature/login_spec.rb index 9555e6785bf..44007d61175 100644 --- a/spec/feature/login_spec.rb +++ b/spec/feature/login_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true RSpec.feature "Login", :all_dbs do - let(:appeal) { create(:legacy_appeal, vacols_case: create(:case)) } + let(:appeal) { create(:legacy_appeal, vacols_case: create(:case_with_ssoc)) } let(:station_id) { "405" } let(:user_email) { "test@example.com" } let(:roles) { ["Certify Appeal"] } @@ -105,18 +105,16 @@ def select_ro_from_dropdown # :nocov: # https://stackoverflow.com/questions/36472930/session-sometimes-not-persisting-in-capybara-selenium-test - scenario "with valid credentials", - skip: "This test sometimes fails because sessions do not persist across requests" do + scenario "with valid credentials" do visit "certifications/new/#{appeal.vacols_id}" expect(page).to have_content("Please select the regional office you are logging in from.") select_ro_from_dropdown click_on "Log in" - expect(page).to have_current_path(new_certification_path(vacols_id: appeal.vacols_id)) + expect(page).to have_current_path("/certifications/#{appeal.vacols_id}/check_documents") expect(find("#menu-trigger")).to have_content("ANNE MERICA (RO05)") end - scenario "logging out redirects to home page", - skip: "This test sometimes fails because sessions do not persist across requests" do + scenario "logging out redirects to home page" do visit "certifications/new/#{appeal.vacols_id}" # vacols login @@ -125,7 +123,7 @@ def select_ro_from_dropdown click_on "Log in" click_on "ANNE MERICA (RO05)" - click_on "Sign out" + click_on "Sign Out" visit "certifications/new/#{appeal.vacols_id}" expect(page).to have_current_path("/login") end @@ -164,12 +162,20 @@ def select_ro_from_dropdown end # :nocov: - scenario "Single Sign On is down", - skip: "This test sometimes fails because it cannot find the expected text" do - Rails.application.config.sso_service_disabled = true - visit "certifications/new/#{appeal.vacols_id}" + context "Single Sign on is down" do + before do + Rails.application.config.sso_service_disabled = true + end - expect(page).to have_content("Login Service Unavailable") + after do + Rails.application.config.sso_service_disabled = false + end + + scenario "it displays the error page" do + visit "certifications/new/#{appeal.vacols_id}" + + expect(page).to have_content("Something went wrong") + end end # :nocov: end diff --git a/spec/feature/queue/motion_to_vacate_spec.rb b/spec/feature/queue/motion_to_vacate_spec.rb index 1841031eaf7..6729215647a 100644 --- a/spec/feature/queue/motion_to_vacate_spec.rb +++ b/spec/feature/queue/motion_to_vacate_spec.rb @@ -678,7 +678,7 @@ def return_to_lit_support(disposition:) it "correctly handles return to judge" do User.authenticate!(user: drafting_attorney) - visit "/queue/appeals/#{vacate_stream.uuid}" + reload_case_detail_page(vacate_stream.uuid) check_cavc_alert verify_cavc_conflict_action @@ -691,7 +691,9 @@ def return_to_lit_support(disposition:) expect(page.current_path).to eq(review_decisions_path) - find(".usa-alert-text").find("a").click + within find(".usa-alert-text") do + click_link("please return to the judge") + end expect(page).to have_content(COPY::MTV_CHECKOUT_RETURN_TO_JUDGE_MODAL_TITLE) expect(page).to have_content(COPY::MTV_CHECKOUT_RETURN_TO_JUDGE_MODAL_DESCRIPTION) diff --git a/spec/feature/queue/pre_docket_spec.rb b/spec/feature/queue/pre_docket_spec.rb index 278122aa539..178e4ad86f3 100644 --- a/spec/feature/queue/pre_docket_spec.rb +++ b/spec/feature/queue/pre_docket_spec.rb @@ -86,7 +86,7 @@ appeal = vha_document_search_task.appeal expect(vha_document_search_task.assigned_to).to eq vha_caregiver - visit "/queue/appeals/#{appeal.external_id}" + reload_case_detail_page(appeal.external_id) expect(page).to have_content("Pre-Docket") expect(page).to have_content(category) @@ -102,7 +102,7 @@ appeal = vha_document_search_task.appeal - visit "/queue/appeals/#{appeal.external_id}" + reload_case_detail_page(appeal.external_id) find(".cf-select__control", text: COPY::TASK_ACTION_DROPDOWN_BOX_LABEL).click find( @@ -136,7 +136,7 @@ appeal = vha_document_search_task.appeal - visit "/queue/appeals/#{appeal.external_id}" + reload_case_detail_page(appeal.external_id) task_name = Constants.TASK_ACTIONS.VHA_CAREGIVER_SUPPORT_RETURN_TO_BOARD_INTAKE.label @@ -224,9 +224,11 @@ expect(appeal.tasks.last.parent.status).to eq Constants.TASK_STATUSES.assigned # Navigate to the appeal that was just returned to board intake and verify the timeline - visit "/queue/appeals/#{appeal.external_id}" + reload_case_detail_page(appeal.external_id) + expect(page).to have_content("Case Timeline") # Click the timeline display link - find(".cf-submit", text: "View task instructions").click + find("#case-timeline-table .cf-submit", text: "View task instructions").click + expect(page).to have_content("Hide task instructions") # Verify the text in the timeline to match the other text field and optional text field. expect(page).to have_content("Other - #{other_text_field_text}") expect(page).to have_content(optional_text_field_text) @@ -240,7 +242,7 @@ appeal = vha_document_search_task.appeal - visit "/queue/appeals/#{appeal.external_id}" + reload_case_detail_page(appeal.external_id) find(".cf-select__control", text: COPY::TASK_ACTION_DROPDOWN_BOX_LABEL).click find( @@ -283,7 +285,7 @@ User.authenticate!(user: bva_intake_user) - visit "/queue/appeals/#{appeal.uuid}" + reload_case_detail_page(appeal.external_id) click_dropdown(text: Constants.TASK_ACTIONS.BVA_INTAKE_RETURN_TO_CAREGIVER.label) @@ -355,7 +357,7 @@ appeal = vha_document_search_task.appeal expect(vha_document_search_task.assigned_to).to eq camo - visit "/queue/appeals/#{appeal.external_id}" + reload_case_detail_page(appeal.external_id) expect(page).to have_content("Pre-Docket") expect(page).to have_content(camo.name) @@ -448,7 +450,7 @@ step "Program Office can assign AssessDocumentationTask to Regional Office" do appeal = Appeal.last - visit "/queue/appeals/#{appeal.external_id}" + reload_case_detail_page(appeal.external_id) dropdown_visn_text = "VISN #{Constants::VISNS_NUMBERED[regional_office.name]} - #{regional_office.name}" @@ -508,7 +510,7 @@ step "Regional Office can send appeal to Program Office as Ready for Review" do appeal = Appeal.last - visit "/queue/appeals/#{appeal.external_id}" + reload_case_detail_page(appeal.external_id) find(".cf-select__control", text: COPY::TASK_ACTION_DROPDOWN_BOX_LABEL).click find( @@ -526,7 +528,7 @@ "?tab=po_completed&#{default_query_params}") appeal = Appeal.last - visit "/queue/appeals/#{appeal.external_id}" + reload_case_detail_page(appeal.external_id) find_all("button", text: COPY::TASK_SNAPSHOT_VIEW_TASK_INSTRUCTIONS_LABEL)[1].click expect(page).to have_content("Documents for this appeal are stored in VBMS") @@ -547,7 +549,7 @@ step "Program Office can send appeal to VHA CAMO as Ready for Review" do appeal = Appeal.last - visit "/queue/appeals/#{appeal.external_id}" + reload_case_detail_page(appeal.external_id) find(".cf-select__control", text: COPY::TASK_ACTION_DROPDOWN_BOX_LABEL).click find( @@ -564,7 +566,7 @@ expect(page).to have_current_path("/organizations/#{program_office.url}"\ "?tab=po_completed&#{default_query_params}") - visit "/queue/appeals/#{appeal.external_id}" + reload_case_detail_page(appeal.external_id) find_all("button", text: COPY::TASK_SNAPSHOT_VIEW_TASK_INSTRUCTIONS_LABEL).first.click expect(page).to have_content("Documents for this appeal are stored in VBMS") expect(page).to have_content(po_instructions) @@ -577,7 +579,7 @@ appeal = vha_document_search_task.appeal - visit "/queue/appeals/#{appeal.external_id}" + reload_case_detail_page(appeal.external_id) task_name = Constants.TASK_ACTIONS.VHA_RETURN_TO_BOARD_INTAKE.label @@ -668,7 +670,7 @@ camo_task.children.each { |task| task.update!(status: "completed") } User.authenticate!(user: camo_user) - visit "/queue/appeals/#{appeal.uuid}" + reload_case_detail_page(appeal.external_id) find(".cf-select__control", text: COPY::TASK_ACTION_DROPDOWN_BOX_LABEL).click find( "div", @@ -701,7 +703,7 @@ User.authenticate!(user: bva_intake_user) - visit "/queue/appeals/#{appeal.uuid}" + reload_case_detail_page(appeal.external_id) find(".cf-select__control", text: COPY::TASK_ACTION_DROPDOWN_BOX_LABEL).click find( @@ -735,7 +737,7 @@ camo_task.completed! User.authenticate!(user: bva_intake_user) - visit "/queue/appeals/#{appeal.external_id}" + reload_case_detail_page(appeal.external_id) bva_intake_dockets_appeal expect(page).to have_content(COPY::DOCKET_APPEAL_CONFIRMATION_TITLE) @@ -777,7 +779,7 @@ camo_task = VhaDocumentSearchTask.last bva_intake_task = PreDocketTask.last - visit "/queue/appeals/#{appeal.external_id}" + reload_case_detail_page(appeal.external_id) bva_intake_dockets_appeal expect(page).to have_content(COPY::DOCKET_APPEAL_CONFIRMATION_TITLE) @@ -1231,7 +1233,7 @@ def bva_intake_dockets_appeal User.authenticate!(user: bva_intake_user) - visit "/queue/appeals/#{emo_task.appeal.uuid}" + reload_case_detail_page(emo_task.appeal.external_id) find(".cf-select__control", text: COPY::TASK_ACTION_DROPDOWN_BOX_LABEL).click find( @@ -1264,7 +1266,7 @@ def bva_intake_dockets_appeal emo_task = create(:education_document_search_task, :assigned, assigned_to: emo) emo_task.completed! - visit "/queue/appeals/#{emo_task.appeal.uuid}" + reload_case_detail_page(emo_task.appeal.external_id) click_dropdown(text: Constants.TASK_ACTIONS.DOCKET_APPEAL.label) diff --git a/spec/models/end_product_establishment_spec.rb b/spec/models/end_product_establishment_spec.rb index 1685fb68b51..e1005d656bb 100644 --- a/spec/models/end_product_establishment_spec.rb +++ b/spec/models/end_product_establishment_spec.rb @@ -855,6 +855,13 @@ it "raises EstablishedEndProductNotFound error" do expect { subject }.to raise_error(EndProductEstablishment::EstablishedEndProductNotFound) end + + it "last_synced_at updates upon error to ensure SyncReviewsJob allows other EPEs to sync before re-attempt" do + expect(end_product_establishment.last_synced_at).to eq(nil) + expect { subject }.to raise_error(EndProductEstablishment::EstablishedEndProductNotFound) + end_product_establishment.reload + expect(end_product_establishment.last_synced_at).to eq(Time.zone.now) + end end context "when a matching end product has been established" do @@ -882,6 +889,13 @@ it "re-raises error" do expect { subject }.to raise_error(BGS::ShareError) end + + it "last_synced_at updates upon error to ensure SyncReviewsJob allows other EPEs to sync before re-attempt" do + expect(end_product_establishment.last_synced_at).to eq(nil) + expect { subject }.to raise_error(BGS::ShareError) + end_product_establishment.reload + expect(end_product_establishment.last_synced_at).to eq(Time.zone.now) + end end context "when the claim_type_code has changed outside of Caseflow" do diff --git a/spec/workflows/post_decision_motion_updater_spec.rb b/spec/workflows/post_decision_motion_updater_spec.rb index 3032c289b93..0aa599cf297 100644 --- a/spec/workflows/post_decision_motion_updater_spec.rb +++ b/spec/workflows/post_decision_motion_updater_spec.rb @@ -66,7 +66,11 @@ verify_vacate_stream end - it "should create decision issues on new vacate" + it "should create decision issues on new vacate" do + subject.process + expect(task.reload.status).to eq Constants.TASK_STATUSES.completed + verify_decision_issues_created + end end context "when vacate type is straight vacate" do