Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,20 @@ copy server/src/configs/sample.config.ts -> server/src/configs/config.ts
yarn --cwd server install
yarn --cwd server test

## Run selenium tests in docker

Configure the remote target BB2 instance where the tested app is registered (as described above "Running the Back-end & Front-end")

Go to local repo base directory, from there run:

docker-compose -f docker-compose.selenium.yml up --abort-on-container-exit

Note: --abort-on-container-exit will abort client and server containers when selenium tests ends

## Visual trouble shoot

Install VNC viewer and point browser to http://localhost:5900 to monitor web UI interactions

## Error Responses and handling:

[See ErrorResponses.md](./ErrorResponses.md)
2 changes: 1 addition & 1 deletion client/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:14.17.1
FROM node:16.17.1

LABEL version="1.0"
LABEL description="Demo of a Medicare claims data sample app"
Expand Down
22 changes: 11 additions & 11 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@
"@types/react-dom": "^17.0.0",
"axios": "^0.21.2",
"http-proxy-middleware": "^1.3.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-router-dom": "^5.2.0",
"react-scripts": "4.0.3",
"sass": "^1.49.7",
"typescript": "^4.1.2",
"web-vitals": "^1.0.1"
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.4.2",
"react-scripts": "5.0.1",
"node-sass": "7.0.3",
"typescript": "^4.8.4",
"web-vitals": "^3.0.3"
},
"scripts": {
"start": "REACT_APP_CTX=docker react-scripts start",
Expand Down Expand Up @@ -45,11 +45,11 @@
]
},
"devDependencies": {
"@testing-library/jest-dom": "^5.16.2",
"@testing-library/react": "^12.1.3",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.4.0",
"@types/react-router-dom": "^5.1.7",
"@types/jest": "^29.1.2",
"@types/react-router-dom": "^5.3.3",
"@typescript-eslint/eslint-plugin": "^5.12.0",
"@typescript-eslint/parser": "^5.12.0",
"eslint-config-react-app": "^7.0.0",
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/patientData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export default function PatientData() {
<div>
<h4>{ header }</h4>
</div>
<Button variation="primary" onClick={goAuthorize}>Authorize</Button>
<Button id="auth_btn" variation="primary" onClick={goAuthorize}>Authorize</Button>
</div>
</div>
);
Expand Down
10,219 changes: 10,219 additions & 0 deletions client/yarn.lock

Large diffs are not rendered by default.

37 changes: 37 additions & 0 deletions docker-compose.selenium.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
version: '3'

services:
server:
build:
context: ./server
dockerfile: ./Dockerfile
environment:
- SELENIUM_TESTS=true
- DANGEROUSLY_DISABLE_HOST_CHECK=true
ports:
- "3001:3001"
- "9229:9229"
client:
build:
context: ./client
dockerfile: ./Dockerfile
environment:
- SELENIUM_TESTS=true
- DANGEROUSLY_DISABLE_HOST_CHECK=true
ports:
- "3000:3000"
selenium-tests:
build:
context: ./selenium_tests
dockerfile: ./Dockerfile
command: pytest ./src/test_node_sample.py
depends_on:
- chrome
- server
- client
chrome:
image: selenium/standalone-chrome-debug
hostname: chrome
ports:
- "4444:4444"
- "5900:5900"
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ services:
context: ./client
dockerfile: ./Dockerfile
ports:
- "3000:3000"
- "3000:3000"
11 changes: 11 additions & 0 deletions selenium_tests/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM selenium/standalone-chrome-debug

ENV PYTHONUNBUFFERED 1
USER root
RUN apt-get update && apt-get install -yq python3.8 python3-pip
RUN mkdir /code
ADD . /code/
WORKDIR /code
RUN ln -s /usr/bin/python3 /usr/local/bin/python
RUN pip3 install --upgrade pip
RUN pip3 install selenium pytest
111 changes: 111 additions & 0 deletions selenium_tests/src/test_node_sample.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# Generated by Selenium IDE
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait


class TestNodeSampleApp():
driver_ready = False

def setup_method(self, method):
if not TestNodeSampleApp.driver_ready:
time.sleep(20)
TestNodeSampleApp.driver_ready = True
print("set driver_ready={}".format(TestNodeSampleApp.driver_ready))
else:
print("driver_ready={}".format(TestNodeSampleApp.driver_ready))

opt = webdriver.ChromeOptions()
opt.add_argument("--disable-dev-shm-usage")
opt.add_argument("--disable-web-security")
opt.add_argument("--allow-running-insecure-content")
opt.add_argument("--no-sandbox")
opt.add_argument("--disable-setuid-sandbox")
opt.add_argument("--disable-webgl")
opt.add_argument("--disable-popup-blocking")
opt.add_argument("--enable-javascript")
opt.add_argument('--allow-insecure-localhost')
opt.add_argument('--window-size=1920,1080')
opt.add_argument("--whitelisted-ips=''")

# opt.add_argument('--headless')
# self.driver = webdriver.Chrome(options=opt)
self.driver = webdriver.Remote(
command_executor='http://chrome:4444/wd/hub', options=opt)

def teardown_method(self, method):
self.driver.quit()

def _find_elem_xpath(self, xpath_expr, **kwargs):
elems = self.driver.find_elements(By.XPATH, xpath_expr)
assert elems is not None
return elems

def _find_and_click(self, timeout_sec, by, by_expr, **kwargs):
elem = WebDriverWait(self.driver, timeout_sec).until(
EC.visibility_of_element_located((by, by_expr)))
assert elem is not None
elem.click()
return elem

def _find_and_return(self, timeout_sec, by, by_expr, **kwargs):
elem = WebDriverWait(self.driver, timeout_sec).until(
EC.visibility_of_element_located((by, by_expr)))
assert elem is not None
return elem

def _find_and_sendkey(self, timeout_sec, by, by_expr, txt, **kwargs):
elem = WebDriverWait(self.driver, timeout_sec).until(
EC.visibility_of_element_located((by, by_expr)))
assert elem is not None
elem.send_keys(txt)
return elem

def _assert_EOB_table_header_present(self):
self._find_and_return(30, By.ID, "column_1")
self._find_and_return(30, By.ID, "column_2")
self._find_and_return(30, By.ID, "column_3")

def _assert_EOB_table_records_present(self, cnt):
xpath = "//table/tbody/tr/td[@data-title='NDC Code']"
elements = self._find_elem_xpath(xpath)
assert len(elements) == cnt

def _input_user_and_passwd_and_login(self):
self._find_and_sendkey(30, By.ID, "username-textbox", "BBUser10000")
self._find_and_sendkey(30, By.ID, "password-textbox", "PW10000!")
self._find_and_click(30, By.ID, "login-button")

def test_node_sample_app_grant_access(self):
self.driver.get("http://client:3000/")
self.driver.set_window_size(1500, 1800)
elem = self._find_and_click(30, By.ID, "auth_btn")
assert elem is not None
self._input_user_and_passwd_and_login()
self._find_and_click(30, By.ID, "approve")
self._assert_EOB_table_header_present()
self._assert_EOB_table_records_present(10)

def test_node_sample_app_grant_access_no_demographic(self):
self.driver.get("http://client:3000/")
self.driver.set_window_size(1500, 1800)
elem = self._find_and_click(30, By.ID, "auth_btn")
assert elem is not None
self._input_user_and_passwd_and_login()
# select radio button "No Demographic Data"
self._find_and_click(30, By.CSS_SELECTOR, "label:nth-child(5)")
self._find_and_click(30, By.ID, "approve")
self._assert_EOB_table_header_present()
self._assert_EOB_table_records_present(10)

def test_node_sample_app_deny_access(self):
self.driver.get("http://client:3000/")
self.driver.set_window_size(1500, 1800)
elem = self._find_and_click(30, By.ID, "auth_btn")
assert elem is not None
self._input_user_and_passwd_and_login()
self._find_and_click(30, By.ID, "deny")
self._assert_EOB_table_header_present()
self._assert_EOB_table_records_present(0)
61 changes: 22 additions & 39 deletions server/src/__tests__/server_test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import axios from "axios";
import { getLoggedInUser } from '../utils/user';
import db from '../utils/db';
import app from '../Server';
import * as reqs from '../utils/request';
import AuthorizationToken from "../entities/AuthorizationToken";

const BB2_BASE_URL = "https://sandbox.bluebutton.cms.gov";

Expand All @@ -23,20 +20,6 @@ const patient = { status: 200, data: { resource: "Patient" } };

const profile = { status: 200, data: { resource: "Profile" } };

const MOCK_AUTH_TOKEN_RESPONSE = {
status: 200,
data: {
access_token: "access_token_foo_refreshed",
expires_in: 36000,
token_type: "Bearer",
scope: ["scope1", "scope2", "scope3"],
refresh_token: "refresh_token_bar_refreshed",
patient: "-19990000000001",
},
};

const MOCK_AUTH_TOKEN = new AuthorizationToken(MOCK_AUTH_TOKEN_RESPONSE.data);

let server: any;

beforeAll(() => {
Expand All @@ -48,11 +31,12 @@ afterAll(() => {
});

test("expect patient end point returns patient data.", async () => {
jest.clearAllMocks();
// mock patient returned at deeper layer
jest.spyOn(reqs, 'get').mockImplementation((url) =>
{
if (url === BB2_PATIENT_URL) {
return Promise.resolve(patient);
return Promise.resolve(patient);
} else {
throw Error("Invalid end point URL: " + url);
}
Expand All @@ -66,6 +50,7 @@ test("expect patient end point returns patient data.", async () => {
});

test("expect profile end point returns profile data.", async () => {
jest.clearAllMocks();
// mock profile returned at lower level get
jest.spyOn(reqs, 'get').mockImplementation((url) =>
{
Expand All @@ -82,11 +67,12 @@ test("expect profile end point returns profile data.", async () => {
});

test("expect coverage end point returns coverage data.", async () => {
jest.clearAllMocks();
// mock coverage returned at deeper layer
jest.spyOn(reqs, 'get').mockImplementation((url) =>
{
if (url === BB2_COVERAGE_URL) {
return Promise.resolve(coverage);
return Promise.resolve(coverage);
} else {
throw Error("Invalid end point URL: " + url);
}
Expand All @@ -99,23 +85,20 @@ test("expect coverage end point returns coverage data.", async () => {
expect(response.data).toEqual(coverage.data);
});

//test("expect eob end point returns eob data.", async () => {
// const loggedInUser = getLoggedInUser(db);
//
// loggedInUser.authToken = MOCK_AUTH_TOKEN;
//
// // mock eob returned at lower level get
// jest.spyOn(reqs, 'get').mockImplementation((url) =>
// {
// if (url === BB2_EOB_URL) {
// return Promise.resolve(eob);
// } else {
// throw Error("Invalid end point URL: " + url);
// }
// }
// );
//
// const response = await axios.get("http://localhost:3003/api/data/benefit-direct");
// expect(response.status).toEqual(200);
// expect(response.data).toEqual(eob.data);
//});
test("expect eob end point returns eob data.", async () => {
jest.clearAllMocks();
// mock eob returned at lower level get
jest.spyOn(reqs, 'get').mockImplementation((url) =>
{
if (url === BB2_EOB_URL) {
return Promise.resolve(eob);
} else {
throw Error("Invalid end point URL: " + url);
}
}
);

const response = await axios.get("http://localhost:3003/api/data/benefit-direct");
expect(response.status).toEqual(200);
expect(response.data).toEqual(eob.data);
});
7 changes: 6 additions & 1 deletion server/src/configs/sample.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
* Replace your client/secret/callback url for each environment below with your specific app details
* (Note: local is mainly for BB2 internal developers)
*/

const SELENIUM_TESTS = process.env.SELENIUM_TESTS;

export type ConfigType = {
[env: string]: {
bb2BaseUrl: string,
Expand All @@ -24,7 +27,9 @@ const config: ConfigType = {
bb2BaseUrl: 'https://sandbox.bluebutton.cms.gov',
bb2ClientId: '<client-id>',
bb2ClientSecret: '<client-secret>',
bb2CallbackUrl: 'http://localhost:3001/api/bluebutton/callback/',
bb2CallbackUrl: SELENIUM_TESTS ?
'http://server:3001/api/bluebutton/callback/'
: 'http://localhost:3001/api/bluebutton/callback/',
},
local: {
bb2BaseUrl: 'https://sandbox.bluebutton.cms.gov',
Expand Down
8 changes: 5 additions & 3 deletions server/src/routes/Authorize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import AuthorizationToken from '../entities/AuthorizationToken';
import Settings from '../entities/Settings';
import db from '../utils/db';
import { getAccessToken, generateAuthorizeUrl } from '../utils/bb2';
import { getBenefitData } from './Data';
import { getBenefitReturnData } from './Data';

const BENE_DENIED_ACCESS = 'access_denied';

Expand Down Expand Up @@ -58,7 +58,7 @@ export async function authorizationCallback(req: Request, res: Response) {
* You could also request data for the Patient endpoint and/or the Coverage endpoint here
* using similar functionality
*/
const eobData = await getBenefitData(req, res);
const eobData = await getBenefitReturnData(req, res);
loggedInUser.eobData = eobData;
} else {
// send generic error message to FE
Expand All @@ -76,7 +76,9 @@ export async function authorizationCallback(req: Request, res: Response) {
* This is a hardcoded redirect, but this should be used from settings stored in a conf file
* or other mechanism
*/
res.redirect('http://localhost:3000');
const fe_redirect_url =
process.env.SELENIUM_TESTS ? 'http://client:3000' : 'http://localhost:3000';
res.redirect(fe_redirect_url);
}

export function getAuthUrl(req: Request, res: Response) {
Expand Down
Loading