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
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ In order to send an email, users must specify the hostname or IP address of an S
- `port`: (Optional) The port to connect to (defaults to 587)
- `to`: Comma separated list or an array of recipients email addresses that will appear on the _To:_ field
- `from`: (Optional) The email address of the sender. All email addresses can be plain `'[email protected]'` or formatted `'"Sender Name" [email protected]'` (defaults to [email protected], which cannot receive reply emails)
- `tlsRejectUnauthorized`: (Optional) A boolean value to set the [node.js TLSSocket option](https://nodejs.org/api/tls.html#tls_class_tls_tlssocket) for rejecting any unauthorized connections, `tls.rejectUnauthorized`. (defaults to `true`)

An example of this object can be found in [`config/csv.config.example.json`](config/csv.config.example.json).

Expand All @@ -126,6 +127,23 @@ Users can specify a different location for the file by using the `--run-log-file
node src/cli/cli.js --run-log-filepath path/to/file.json
```

### Masking Patient Data

Currently, patient data can be masked within the extracted `Patient` resource. When masked, the value of the field will be replaced with a [Data Absent Reason extension](https://www.hl7.org/fhir/extension-data-absent-reason.html) with the code `masked`.
Patient properties that can be masked are: `gender`, `mrn`, `name`, `address`, `birthDate`, `language`, `ethnicity`, `birthsex`, and `race`.
To mask a property, provide an array of the properties to mask in the `constructorArgs` of the Patient extractor. For example, the following configuration can be used to mask `address` and `birthDate`:

```bash
{
"label": "patient",
"type": "CSVPatientExtractor",
"constructorArgs": {
"filePath": "./data/patient-information.csv"
"mask": ["address", "birthDate"]
}
}
```

### Extraction Date Range

The mCODE Extraction Client will extract all data that is provided in the CSV files by default, regardless of any dates associated with each row of data. It is recommended that any required date filtering is performed outside of the scope of this client.
Expand Down
3 changes: 2 additions & 1 deletion config/csv.config.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"to": [
"[email protected]",
"[email protected]"
]
],
"tlsRejectUnauthorized": true
},
"extractors": [
{
Expand Down
6 changes: 6 additions & 0 deletions src/cli/emailNotifications.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,15 @@ async function sendEmailNotification(notificationInfo, errors, debug = false) {
emailBody += 'The stack trace information can be seen in the terminal as well as in the notification email.';
}

// Ensure that the tlsRejectUnauthorized property is a boolean
if (notificationInfo.tlsRejectUnauthorized && (notificationInfo.tlsRejectUnauthorized !== true || notificationInfo.tlsRejectUnauthorized !== false)) {
logger.warn('The notificationInfo.tlsRejectUnauthorized should be a boolean value. The value provided will not be used.');
}

const transporter = nodemailer.createTransport({
host: notificationInfo.host,
...(notificationInfo.port && { port: notificationInfo.port }),
...((notificationInfo.tlsRejectUnauthorized === true || notificationInfo.tlsRejectUnauthorized === false) && { tls: { rejectUnauthorized: notificationInfo.tlsRejectUnauthorized } }),
});

logger.debug('Sending email with error information');
Expand Down
45 changes: 44 additions & 1 deletion test/cli/emailNotifications.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ describe('sendEmailNotification', () => {
port: 123,
to: ['[email protected]', '[email protected]'],
from: '[email protected]',
tlsRejectUnauthorized: false,
};
const errors = {
0: [],
Expand All @@ -79,7 +80,7 @@ describe('sendEmailNotification', () => {
};

await expect(sendEmailNotification(notificationInfo, errors, false)).resolves.not.toThrow();
expect(createTransportSpy).toBeCalledWith({ host: notificationInfo.host, port: notificationInfo.port });
expect(createTransportSpy).toBeCalledWith({ host: notificationInfo.host, port: notificationInfo.port, tls: { rejectUnauthorized: notificationInfo.tlsRejectUnauthorized } });
expect(sendMailMock).toBeCalled();
const sendMailMockArgs = sendMailMock.mock.calls[0][0];
expect(sendMailMockArgs.to).toEqual(notificationInfo.to);
Expand All @@ -93,6 +94,48 @@ describe('sendEmailNotification', () => {
expect(sendMailMockArgs.text).not.toMatch(/Error at line 4`/i);
});

it('should send an email with tlsRejectUnauthorized set to true, false, and not set', async () => {
const notificationInfoTLSFalse = {
host: 'my.host.com',
to: ['[email protected]', '[email protected]'],
tlsRejectUnauthorized: false,
};
const notificationInfoTLSTrue = {
host: 'my.host.com',
to: ['[email protected]', '[email protected]'],
tlsRejectUnauthorized: true,
};
const notificationInfoNoTLS = {
host: 'my.host.com',
to: ['[email protected]', '[email protected]'],
};
const notificationInfoUnexpectedTLS = {
host: 'my.host.com',
to: ['[email protected]', '[email protected]'],
tlsRejectUnauthorized: 'true', // Any value that is not true or false will log a warning and not be used
};
const errors = {
0: [],
1: [{ message: 'something bad', stack: 'Error at line 1' }],
};
createTransportSpy.mockReturnValueOnce({ sendMail: sendMailMock });
await expect(sendEmailNotification(notificationInfoTLSFalse, errors, false)).resolves.not.toThrow();
expect(createTransportSpy).toBeCalledWith({ host: notificationInfoTLSFalse.host, port: notificationInfoTLSFalse.port, tls: { rejectUnauthorized: false } });

createTransportSpy.mockReturnValueOnce({ sendMail: sendMailMock });
await expect(sendEmailNotification(notificationInfoTLSTrue, errors, false)).resolves.not.toThrow();
expect(createTransportSpy).toBeCalledWith({ host: notificationInfoTLSTrue.host, port: notificationInfoTLSTrue.port, tls: { rejectUnauthorized: true } });

createTransportSpy.mockReturnValueOnce({ sendMail: sendMailMock });
await expect(sendEmailNotification(notificationInfoNoTLS, errors, false)).resolves.not.toThrow();
expect(createTransportSpy).toBeCalledWith({ host: notificationInfoNoTLS.host, port: notificationInfoNoTLS.port }); // No tls object set

createTransportSpy.mockReturnValueOnce({ sendMail: sendMailMock });
await expect(sendEmailNotification(notificationInfoUnexpectedTLS, errors, false)).resolves.not.toThrow();
// A warning will be logged and the unexpected value will not be used
expect(createTransportSpy).toBeCalledWith({ host: notificationInfoUnexpectedTLS.host, port: notificationInfoUnexpectedTLS.port });
});

it('should send an email with stack traces if debug flag was used', async () => {
createTransportSpy.mockReturnValueOnce({ sendMail: sendMailMock });
const notificationInfo = {
Expand Down