Unverified Commit 1598b7b3 authored by Philipp Berger's avatar Philipp Berger
Browse files

chore: release v2.3.0

parent 9472f915
# Changelog
### 2.3.0 (2021-10-18)
* **backend** fix: change GET to POST for api/v3/userTransfers/tan/
* **backend** fix: missing name of location in share data email
* **backend** fix: multiple activation links being valid at the same time
* **backend** fix: metrics path label for errors
* **backend** feat: limit number of available tables
* **backend** feat: limit number of checkin questions
* **backend** feat: implement level 4 notifications
* **backend** feat: keep daily keys for 35 days
* **backend** feat: update ip lists from external sources
* **backend** feat: add new events to audit log system
* **backend** chore: replace winston with pino
* **backend** chore: seperate private meeting endpoints
* **health-department** feat: prevent unsigned HDs from requesting data from locations
* **locations** fix: two emails are sent for sharing data request
* **locations** fix: share data link in mail leads to blank page when operator is not logged in
* **locations** fix: footer might overlap on small devices
* **locations** fix: wording in check-out reminder
* **locations** fix: QR-Print instructions text and alignment
* **locations** fix: wording in checkin options to be consistent
* **locations** fix: cursor property for change location while hovering
* **locations** fix: German diacritic letters are not counted as upper/lower characters
* **locations** feat: new registration/login flow
* **locations** feat: update privacy policy documents
* **locations** feat: update AVV document
* **locations** feat: edit address for existing areas and locations
* **locations** feat: move links from profile page to help center
* chore: replace react-helmet in all frontend services
### 2.2.0 (2021-10-01)
* **backend** feat: add operator device support
* **backend** feat: add operator device feature flag
......
......@@ -257,7 +257,7 @@ void e2eTest() {
sh("IMAGE_TAG=e2e_${UNIQUE_TAG} docker-compose -f docker-compose.yml run backend yarn migrate")
sh("IMAGE_TAG=e2e_${UNIQUE_TAG} docker-compose -f docker-compose.yml run backend yarn seed")
sh("IMAGE_TAG=e2e_${UNIQUE_TAG} SKIP_SMS_VERIFICATION=true E2E=true docker-compose -f docker-compose.yml up -d")
sh("docker run --rm --network=host --ipc=host --entrypoint='' -v `pwd`/e2e:/e2e -w /e2e cypress/included:7.3.0 /bin/bash -c 'npx wait-on https://127.0.0.1/api/v3/keys/daily/ -t 30000 && yarn install && yarn cache clean && cypress run' ")
sh("docker run --rm --network=host --ipc=host --entrypoint='' -v `pwd`/e2e:/e2e -w /e2e cypress/included:8.4.0 /bin/bash -c 'npx wait-on https://127.0.0.1/api/v3/keys/daily/ -t 30000 && yarn install && yarn cache clean && cypress run' ")
sh("IMAGE_TAG=e2e_${UNIQUE_TAG} docker-compose -f docker-compose.yml down --rmi all -v -t 0 ")
}
} finally {
......
......@@ -21,6 +21,9 @@ services:
environment:
- SKIP_SMS_VERIFICATION
- E2E
- DENY_LIST_NETSET_URLS
- DENY_LIST_SINGLE_IP_CSV_URLS
- DENY_LIST_DOUBLE_IP_CSV_URLS
locations:
image: lucaapp/locations:${IMAGE_TAG}
build:
......
......@@ -23,4 +23,5 @@ cypress/screenshots
cypress/plugins/downloads
# certificates
certs/*.pfx
\ No newline at end of file
certs/*.pfx
certs/*.txt
\ No newline at end of file
......@@ -4,23 +4,31 @@
"testFiles": [
"workflow/**/*.spec.js",
"webapp/**/*.spec.js",
"locations/downloadPDF/downloadPDF.spec.js",
"locations/**/*.spec.js",
"health-department/**/*.spec.js",
"scanner/**/*.spec.js",
"locations/authentication/**/*.spec.js",
"locations/checkinOptions/**/*.spec.js",
"locations/group/**/*.spec.js",
"locations/location/**/*.spec.js",
"locations/locationOverview/*.spec.js",
"locations/locationSettings/*.spec.js",
"locations/navigation/*.spec.js",
"locations/profile/*.spec.js"
"contact-form/**/*.spec.js"
],
"integrationFolder": "./specs",
"video": false,
"supportFile": "./support",
"downloadsFolder": "./downloads",
"viewportHeight": 1080,
"viewportWidth": 1920,
"env": {
"envName": "local"
}
},
"clientCertificates": [
{
"url": "https://localhost:9443/**",
"ca": [],
"certs": [
{
"pfx": "./certs/health.pfx",
"passphrase": "./certs/health-passphrase.txt"
}
]
}
],
"chromeWebSecurity": false
}
......@@ -64,6 +64,10 @@ module.exports = (on, config) => {
launchOptions.args.push(
'--use-file-for-fake-video-capture=/tmp/luca/stream.mjpeg'
);
launchOptions.args.push('--disable-site-isolation-trials');
launchOptions.args.push(
'--disable-features=CrossSiteDocumentBlockingIfIsolating,CrossSiteDocumentBlockingAlways,IsolateOrigins,site-per-process'
);
launchOptions.preferences.default['download'] = {
default_directory: path.join(__dirname, 'downloads'),
};
......
{
"name": "e2e",
"version": "2.2.0",
"version": "2.3.0",
"main": "index.js",
"private": true,
"engines": {
......@@ -14,7 +14,7 @@
},
"dependencies": {
"@lucaapp/crypto": "1.0.3",
"cypress": "7.4.0",
"cypress": "8.4.0",
"cypress-file-upload": "5.0.7",
"faker": "5.5.3",
"fluent-ffmpeg": "2.1.2",
......
......@@ -2,15 +2,15 @@ import {
E2E_HEALTH_DEPARTMENT_USERNAME,
E2E_HEALTH_DEPARTMENT_PASSWORD,
} from '../../helper/user';
import { logout } from '../../helper/api/auth.helper';
import { RESET_HD_KEYS_QUERY } from '../../helper/dbQueries';
import {
loginToHD,
openHDLoginPage,
downloadHealthDepartmentPrivateKey,
} from '../../helper/ui/login.helper';
import { HEALTH_DEPARTMENT_APP_ROUTE } from '../../helper/routes';
import { signHealthDepartment } from '../../helper/signHealthDepartment';
describe('Autentication', () => {
describe('Authentication', () => {
before(() => {
cy.executeQuery(RESET_HD_KEYS_QUERY);
});
......@@ -22,11 +22,11 @@ describe('Autentication', () => {
describe('Health Department / Authentication / Login', () => {
describe('when a user login for the first time with correct password', () => {
it('ask to download private key and redirect to tracking page', () => {
loginToHD(
E2E_HEALTH_DEPARTMENT_USERNAME,
E2E_HEALTH_DEPARTMENT_PASSWORD
);
cy.contains('Wrong Email or password').should('not.exist');
cy.basicLoginHD().then(response => {
expect(response.status).to.eq(200);
});
cy.visit(HEALTH_DEPARTMENT_APP_ROUTE);
signHealthDepartment();
cy.url().should('include', '/app/tracking');
cy.get('.ant-modal').within(() => {
cy.contains('Setup').should('exist');
......@@ -49,17 +49,24 @@ describe('Autentication', () => {
});
describe('when a user login with incorrect password', () => {
it('error message is shown', () => {
loginToHD(E2E_HEALTH_DEPARTMENT_USERNAME, 'invalid');
cy.contains('Wrong Email or password').should('exist');
cy.basicLoginHD(E2E_HEALTH_DEPARTMENT_USERNAME, 'invalid').then(
response => {
expect(response.status).to.eq(401);
}
);
});
});
describe('when an not existent user tries to login', () => {
it('error message is shown', () => {
loginToHD('invalid', E2E_HEALTH_DEPARTMENT_PASSWORD);
cy.contains('Wrong Email or password').should('exist');
cy.basicLoginHD(
'invalid@nexenio.com',
E2E_HEALTH_DEPARTMENT_PASSWORD
).then(response => {
expect(response.status).to.eq(401);
});
});
});
});
afterEach(() => logout());
afterEach(() => cy.logoutHD());
});
import { loginHealthDepartment } from '../../helper/api/auth.helper';
import {
E2E_HEALTH_DEPARTMENT_USERNAME,
E2E_HEALTH_DEPARTMENT_PASSWORD,
} from '../../helper/user';
import { logout } from '../../helper/api/auth.helper';
import {
loginToHD,
openHDLoginPage,
uploadWrongHealthDepartmentPrivateKeyFileType,
uploadWrongHealthDepartmentPrivateKeyFile,
uploadWrongHealthDepartmentPrivateKeyFileTypeReUploadCorrectFile,
uploadHealthDepartmentPrivateKeyFileLargeSize,
} from '../../helper/ui/login.helper';
const appTracking = '/app/tracking';
const testSetup = () => {
openHDLoginPage();
loginToHD(E2E_HEALTH_DEPARTMENT_USERNAME, E2E_HEALTH_DEPARTMENT_PASSWORD);
cy.url().should('include', appTracking);
};
describe('Authentication', () => {
describe('Health Department / Authentication / Login / Private key upload', () => {
describe('When uploading a wrong key', () => {
describe('When Uploading private key file that is too large', () => {
it('A notification occours stating that the key is too large', () => {
testSetup();
loginHealthDepartment();
uploadHealthDepartmentPrivateKeyFileLargeSize();
cy.get('.ant-notification-notice', { timeout: 10000 }).should(
'be.visible'
);
cy.get('.ant-modal').should('exist');
logout();
cy.logoutHD();
});
});
describe('When uploading a Private key that has the wrong key format', () => {
it('A notification occours stating that a wrong key file has been uploaded', () => {
testSetup();
loginHealthDepartment();
uploadWrongHealthDepartmentPrivateKeyFileType();
cy.get('.ant-notification-notice', { timeout: 10000 }).should(
'be.visible'
);
cy.get('.ant-modal').should('exist');
logout();
cy.logoutHD();
});
});
describe('When uploading a wrong Private key', () => {
it('it will reject the private key file and a notification should occur stating that a wrong key has been uploaded', () => {
testSetup();
loginHealthDepartment();
uploadWrongHealthDepartmentPrivateKeyFile();
cy.get('.ant-notification-notice', { timeout: 10000 }).should(
'be.visible'
);
cy.get('.ant-modal').should('exist');
logout();
cy.logoutHD();
});
});
});
describe('Try again key Re-upload', () => {
describe('When uploading a Wrong private key and afterwards re-upload a correct private key', () => {
it('it should reject the key upload and a notification should show stating that a wrong key has been uploaded, after that the correct private key is being uploaded and a notification occurs stating the the private key has been successfully uploaded as well the modal should close', () => {
testSetup();
loginHealthDepartment();
uploadWrongHealthDepartmentPrivateKeyFileTypeReUploadCorrectFile();
cy.getByCy('header')
.contains('Health-Department')
......@@ -71,7 +57,7 @@ describe('Authentication', () => {
cy.get('.ant-menu-horizontal').should('exist').should('be.visible');
cy.getByCy('navigation').should('exist').should('be.visible');
logout();
cy.logoutHD();
});
});
});
......
import {
E2E_HEALTH_DEPARTMENT_USERNAME,
E2E_HEALTH_DEPARTMENT_PASSWORD,
} from '../../helper/user';
import { logout } from '../../helper/api/auth.helper';
import {
loginToHD,
openHDLoginPage,
addHealthDepartmentPrivateKeyFile,
} from '../../helper/ui/login.helper';
import { loginHealthDepartment } from '../../helper/api/auth.helper';
import { addHealthDepartmentPrivateKeyFile } from '../../helper/ui/login.helper';
describe('Autentication', () => {
describe('Health Department / Authentication / Login', () => {
describe('when a user login for the second time', () => {
it('ask to upload private key and redirect to tracking page', () => {
openHDLoginPage();
loginToHD(
E2E_HEALTH_DEPARTMENT_USERNAME,
E2E_HEALTH_DEPARTMENT_PASSWORD
);
cy.contains('Wrong Email or password').should('not.exist');
cy.url().should('include', '/app/tracking');
loginHealthDepartment();
addHealthDepartmentPrivateKeyFile();
cy.getByCy('header')
.contains('Health-Department')
......@@ -30,7 +16,7 @@ describe('Autentication', () => {
cy.get('.ant-menu-horizontal').should('exist').should('be.visible');
cy.getByCy('navigation').should('exist').should('be.visible');
logout();
cy.logoutHD();
});
});
});
......
......@@ -7,12 +7,9 @@ describe('Health Department / Authentication / Logout', () => {
addHealthDepartmentPrivateKeyFile();
});
describe('when an operator logs out', () => {
it('redirect to Login page', () => {
cy.getByCy('logout').should('exist').should('be.visible').click();
cy.get('form.ant-form').within(($form) => {
cy.get('#username').should('exist').should('be.visible');
cy.get('#password').should('exist').should('be.visible');
cy.get('button[type=submit]').should('exist').should('be.visible');
it('logs the user out successfully', () => {
cy.logoutHD().then(response => {
expect(response.status).to.eq(204);
});
});
});
......
import { login, logout, createGroup, deleteGroup } from '../../locations/helpers/functions';
import { loginHealthDepartment } from '../helper/api/auth.helper';
import { addHealthDepartmentPrivateKeyFile } from '../helper/ui/login.helper';
import { createGroupPayload } from '../../locations/helpers/functions.helper';
import { createGroupPayload } from '../../locations/utils/payloads.helper';
import { APP_ROUTE } from '../../locations/constants/routes';
import { deleteGroup } from '../../locations/utils/groups';
import { signHealthDepartment } from '../helper/signHealthDepartment';
const FIRST_GROUP_NAME = 'test group first';
const SECOND_GROUP_NAME = 'second test@group';
......@@ -12,41 +14,65 @@ const GROUP_IDS = [];
const GROUP_PREFIX = 'group_';
const SEARCH_TERM = 'tes';
describe('Health Department / Group search / Search for a group', () => {
//create groups with different names
before(() => {
login();
cy.basicLoginLocations();
GROUPS.forEach(group => {
createGroup({ ...createGroupPayload, name: group });
cy.createGroup({ ...createGroupPayload, name: group });
cy.get('@groupId').then(groupId => {
GROUP_IDS.push(groupId);
});
});
logout();
cy.logoutLocations();
});
//delete created groups
after(() => {
login();
cy.basicLoginLocations();
cy.visit(APP_ROUTE);
cy.url().should('contain', APP_ROUTE);
GROUP_IDS.forEach(id => deleteGroup(id));
});
describe('when searching for groups by name', () => {
it('display groups matching search term', () => {
loginHealthDepartment();
signHealthDepartment();
addHealthDepartmentPrivateKeyFile();
cy.getByCy('searchGroup').should('exist').should('be.visible').click();
//search group by term
cy.get('.ant-modal-content').within(($modal) => {
cy.getByCy('groupNameInput').should('exist').should('be.visible').type(SEARCH_TERM);
cy.getByCy('startGroupSearch').should('exist').should('be.visible').click();
//verify 2 groups are found and one is noot
MATCHED_GROUPS.forEach((group) => {
cy.getByCy(GROUP_PREFIX + group).should('exist').should('be.visible');
cy.getByCy(GROUP_PREFIX + group).children().then(($group) => {
expect($group.text()).contains(group);
expect($group.text()).contains(createGroupPayload.streetName + ' ' + createGroupPayload.streetNr + ', ' + createGroupPayload.zipCode + ' ' + createGroupPayload.city);
});
cy.get('.ant-modal-content').within($modal => {
cy.getByCy('groupNameInput')
.should('exist')
.should('be.visible')
.type(SEARCH_TERM);
cy.getByCy('startGroupSearch')
.should('exist')
.should('be.visible')
.click();
// verify 2 groups are found and one is not
MATCHED_GROUPS.forEach(group => {
cy.getByCy(GROUP_PREFIX + group)
.first()
.scrollIntoView()
.should('exist')
.should('be.visible');
cy.getByCy(GROUP_PREFIX + group)
.first()
.scrollIntoView()
.children()
.then($group => {
expect($group.text()).contains(group);
expect($group.text()).contains(
createGroupPayload.streetName +
' ' +
createGroupPayload.streetNr +
', ' +
createGroupPayload.zipCode +
' ' +
createGroupPayload.city
);
});
});
cy.getByCy(GROUP_PREFIX + THIRD_GROUP_NAME).should('not.exist');
});
......
import { login, logout, createGroup, deleteGroup } from '../../locations/helpers/functions';
import { loginHealthDepartment } from '../helper/api/auth.helper';
import { addHealthDepartmentPrivateKeyFile } from '../helper/ui/login.helper';
import { createGroupPayload } from '../../locations/helpers/functions.helper';
import { createGroupPayload } from '../../locations/utils/payloads.helper';
import { APP_ROUTE } from '../../locations/constants/routes';
import { deleteGroup } from '../../locations/utils/groups';
import { signHealthDepartment } from '../helper/signHealthDepartment';
const FIRST_GROUP_NAME = 'test group first';
const SECOND_GROUP_NAME = 'test group second';
const THIRD_GROUP_NAME = 'not matching group';
const ZIP_CODE_MATCHED = '12345';
const ZIP_CODE_MISMATCHED = '54321';
const GROUP_ZIP_MAP = { [FIRST_GROUP_NAME]: ZIP_CODE_MATCHED, [SECOND_GROUP_NAME]: ZIP_CODE_MISMATCHED, [THIRD_GROUP_NAME]: ZIP_CODE_MATCHED };
const GROUP_ZIP_MAP = {
[FIRST_GROUP_NAME]: ZIP_CODE_MATCHED,
[SECOND_GROUP_NAME]: ZIP_CODE_MISMATCHED,
[THIRD_GROUP_NAME]: ZIP_CODE_MATCHED,
};
const GROUP_IDS = [];
const GROUP_PREFIX = 'group_';
const SEARCH_TERM = 'tes';
describe('Health Department / Group search / Search for a group by Name and Zip code', () => {
//create groups with different names and zip codes
before(() => {
login();
cy.basicLoginLocations();
for (const [key, value] of Object.entries(GROUP_ZIP_MAP)) {
createGroup({ ...createGroupPayload, name: key, zipCode: value });
cy.createGroup({ ...createGroupPayload, name: key, zipCode: value });
cy.get('@groupId').then(groupId => {
GROUP_IDS.push(groupId);
});
};
logout();
}
cy.logoutLocations();
});
beforeEach(() => {
loginHealthDepartment();
signHealthDepartment();
addHealthDepartmentPrivateKeyFile();
});
//delete created groups
after(() => {
login();
cy.basicLoginLocations();
cy.visit(APP_ROUTE);
cy.url().should('contain', APP_ROUTE);
GROUP_IDS.forEach(id => deleteGroup(id));
});
......@@ -42,8 +50,11 @@ describe('Health Department / Group search / Search for a group by Name and Zip
it('disables the search button', () => {
cy.getByCy('searchGroup').should('exist').should('be.visible').click();
//search group by term
cy.get('.ant-modal-content').within(($modal) => {
cy.get('#zipCode').should('exist').should('be.visible').type(ZIP_CODE_MATCHED);
cy.get('.ant-modal-content').within($modal => {
cy.get('#zipCode')
.should('exist')
.should('be.visible')
.type(ZIP_CODE_MATCHED);
cy.getByCy('startGroupSearch').should('exist').should('be.disabled');
});
});
......@@ -53,21 +64,35 @@ describe('Health Department / Group search / Search for a group by Name and Zip
it('displays groups matching search criteria', () => {
cy.getByCy('searchGroup').should('exist').should('be.visible').click();
//search groups by name and zip
cy.get('.ant-modal-content').within(($modal) => {
cy.getByCy('groupNameInput').should('exist').should('be.visible').type(SEARCH_TERM);
cy.get('#zipCode').should('exist').should('be.visible').type(ZIP_CODE_MATCHED);
cy.getByCy('startGroupSearch').should('exist').should('be.visible').click();
cy.get('.ant-modal-content').within($modal => {
cy.getByCy('groupNameInput')
.should('exist')
.should('be.visible')
.type(SEARCH_TERM);
cy.get('#zipCode')
.should('exist')
.should('be.visible')
.type(ZIP_CODE_MATCHED);
cy.getByCy('startGroupSearch')
.should('exist')
.should('be.visible')
.click();
//verify one group matching name and zip is found
cy.getByCy(GROUP_PREFIX + FIRST_GROUP_NAME).should('exist').should('be.visible');
cy.getByCy(GROUP_PREFIX + FIRST_GROUP_NAME).children().then(($group) => {
expect($group.text()).contains(FIRST_GROUP_NAME);
expect($group.text()).contains(`${createGroupPayload.streetName} ${createGroupPayload.streetNr}, ${ZIP_CODE_MATCHED} ${createGroupPayload.city}`);
});
cy.getByCy(GROUP_PREFIX + FIRST_GROUP_NAME)
.should('exist')
.should('be.visible');
cy.getByCy(GROUP_PREFIX + FIRST_GROUP_NAME)
.children()
.then($group => {
expect($group.text()).contains(FIRST_GROUP_NAME);
expect($group.text()).contains(
`${createGroupPayload.streetName} ${createGroupPayload.streetNr}, ${ZIP_CODE_MATCHED} ${createGroupPayload.city}`
);
});
//verify groups with different name or zip are not found
cy.getByCy(GROUP_PREFIX + SECOND_GROUP_NAME).should('not.exist');
cy.getByCy(GROUP_PREFIX + THIRD_GROUP_NAME).should('not.exist');
});
});
});
});
\ No newline at end of file
});
......@@ -4,35 +4,17 @@ import {
E2E_HEALTH_DEPARTMENT_PASSWORD,
} from '../user';
export const loginHealthDepartment = () => {
cy.request({
method: 'POST',
url: 'api/v3/auth/healthDepartmentEmployee/login',
body: {
username: E2E_HEALTH_DEPARTMENT_USERNAME,
password: E2E_HEALTH_DEPARTMENT_PASSWORD,
},
headers: {
origin: Cypress.config().baseUrl,
host: Cypress.env('host'),
},
});
cy.visit(HEALTH_DEPARTMENT_APP_ROUTE);
};
const appTracking = '/app/tracking';