├── .github └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── client ├── api_client.js ├── authentication.js └── notification.js ├── index.js ├── package-lock.json ├── package.json ├── scripts ├── run_with_docker.sh └── test_send.js └── spec ├── api_client.js ├── authentication.js ├── integration ├── schemas │ ├── v1 │ │ ├── GET_notifications_return.json │ │ ├── POST_notification_return_email.json │ │ ├── POST_notification_return_sms.json │ │ ├── definitions.json │ │ ├── email_notification.json │ │ └── sms_notification.json │ └── v2 │ │ ├── GET_notification_response.json │ │ ├── GET_notifications_response.json │ │ ├── GET_received_text_response.json │ │ ├── GET_received_texts_response.json │ │ ├── GET_template_by_id.json │ │ ├── GET_templates_response.json │ │ ├── POST_notification_email_response.json │ │ ├── POST_notification_letter_response.json │ │ ├── POST_notification_precompiled_letter_response.json │ │ ├── POST_notification_sms_response.json │ │ ├── POST_template_preview.json │ │ └── definitions.json ├── test.js └── test_files │ └── one_page_pdf.pdf ├── notification.js └── test_files └── simple.csv /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## What problem does the pull request solve? 4 | 5 | 6 | ## Checklist 7 | 8 | 9 | - [x] I’ve used the pull request template 10 | - [ ] I’ve written unit tests for these changes 11 | - [ ] I’ve updated the documentation in 12 | - [notifications-tech-docs repository](https://github.com/alphagov/notifications-tech-docs/blob/main/source/documentation/client_docs/_node.md) 13 | - `CHANGELOG.md` 14 | - [ ] I’ve bumped the version number in 15 | - `package.json` 16 | - [ ] I've added new environment variables in 17 | - `CONTRIBUTING.md` 18 | - `notifications-node-client/scripts/generate_docker_env.sh` 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea/ 3 | environment.sh 4 | .eslintrc.js 5 | docker.env 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 8.2.1 - 2024-07-03 2 | 3 | * Fix a bug where an internal API client function isn't being exposed in the library. 4 | 5 | ## 8.2.0 - 2024-05-17 6 | 7 | * Add support for providing a custom underlying Axios client via `setClient`. 8 | 9 | ## 8.1.0 - 2024-05-09 10 | 11 | * The `sendEmail` function can now be passed `oneClickUnsubscribeURL` as an optional argument. 12 | 13 | ## 8.0.0 - 2023-12-27 14 | 15 | * Remove the default `is_csv` boolean parameter from `prepareUpload`. This method now accepts a file and an options map with the following options. For more specific information please read the documentation. 16 | * `filename` (string) - specify the document's filename upon download 17 | * `confirm_email_before_download` (boolean) - require the user to enter their email address before the file can be downloaded. 18 | * `retention_period` (string) - how long Notify should make the file available to the user for. 19 | 20 | ## 7.0.6 - 2023-11-13 21 | 22 | * Bump axios from 1.2.6 to 1.6.1 23 | 24 | ## 7.0.5 - 2023-11-13 25 | 26 | * Fix a few cases of assignment to undeclared (global) variables 27 | 28 | ## 7.0.4 - 2023-11-10 29 | 30 | * Bump axios to the 1.x branch to address CVE-2023-45857. Due to underlying changes in Axios you may have to explicitly set the `protocol` property when constructing your `proxyConfig` object, if using a proxy. 31 | 32 | ## 7.0.3 - 2023-07-21 33 | 34 | * Bump word-wrap from 1.2.3 to 1.2.4 to address CVE-2023-26115. 35 | 36 | ## 7.0.2 - 2023-07-13 37 | 38 | * Bump semver from 5.7.1 to 5.7.2 39 | 40 | ## 7.0.1 - 2023-07-13 41 | 42 | * Fix a bug with default behaviour for `confirmEmailBeforeDownload`, which coalesced false to null. 43 | 44 | ## 7.0.0 - 2023-01-12 45 | 46 | * Remove support for node versions below v14.17.3. 47 | 48 | ## 6.0.0 - 2022-12-22 49 | 50 | * Bump jsonwebtokens from 8.5.1 to 9.0.0 to mitigate CVE-2022-23529. We don't believe this CVE affects any use-cases of notifications-node-client; this update is out of best-practice rather than any direct concern. 51 | * Remove support for node versions below v12. 52 | 53 | ## 5.2.3 - 2022-11-22 54 | 55 | * Bump follow-redirects from 1.14.7 to 1.15.2 56 | 57 | ## 5.2.2 - 2022-11-16 58 | 59 | * Upgrade ansi-regex dependencies to mitigate CVE-2021-3807. 60 | 61 | ## 5.2.1 - 2022-10-19 62 | 63 | * Support strings in calls to `prepareUpload`. `fs.readFile` can return strings if an encoding is provided, and the client didn't handle these correctly. 64 | 65 | ## 5.2.0 - 2022-09-27 66 | 67 | * Add support for new security features when sending a file by email: 68 | * `confirmEmailBeforeDownload` can be set to `true` to require the user to enter their email address before accessing the file. 69 | * `retentionPeriod` can be set to `<1-78> weeks` to set how long the file should be made available. 70 | 71 | * The `isCsv` parameter to `prepareUpload` has now been replaced by an `options` parameter. The implementation has been done in a backwards-compatible way, so if you are just sending `true/false` values as the seecond parameter, that will continue to work. Though we still recommend updating to use the new options format. 72 | 73 | 74 | ## 5.1.2 - 2022-09-23 75 | 76 | Remove underscore.js dependencyr new send a file features) 77 | 78 | ## 5.1.1 - 2022-01-18 79 | 80 | Upgrade axios version from ^0.21.1 to ^0.25.0 81 | 82 | ## 5.1.0 - 2020-12-30 83 | 84 | ### Changed 85 | 86 | * Upgrade axios version from 0.19.2 to 0.21.1 87 | * Allow any compatible version of axios to be used (0.21.1 to <1.0.0) 88 | * Allow any compatible version of jsonwebtoken to be used (8.2.1 to <9.0.0) 89 | 90 | ## 5.0.2 - 2020-11-20 91 | 92 | ### Changed 93 | 94 | Correct incorrect description of parameter to be used by `NotifyClient.setProxy` 95 | 96 | ## 5.0.1 - 2020-11-18 97 | 98 | ### Changed 99 | 100 | Remove unintentional global nature of variable `version` 101 | 102 | ## 5.0.0 - 2020-09-02 103 | 104 | ### Changed 105 | 106 | We have replaced the use of the npm [request-promise](https://www.npmjs.com/package/request-promise) package with [axios](https://www.npmjs.com/package/axios) as the npm [request](https://www.npmjs.com/package/request) package has been deprecated. This makes the following breaking changes: 107 | 108 | 1. The `response` `object` returned by a successful API call is now in the form of an [axios response](https://www.npmjs.com/package/axios#response-schema). This has a different interface to a [request response](https://nodejs.org/api/http.html#http_class_http_incomingmessage). For example: 109 | 110 | * `response.body` becomes `response.data` 111 | * `response.statusCode` becomes `response.status` 112 | 113 | 2. The `err` `object` returned by an unsuccessful API call has a different interface. For example, `err.error` becomes `err.response.data`. See the axios documentation for further details on [error handling](https://www.npmjs.com/package/axios#handling-errors). 114 | 115 | 3. To configure the use of a proxy you should pass the proxy configuration as an `object` rather than a URL. For details, see the [axios client](https://github.com/axios/axios). 116 | 117 | 4. We now return native [promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises) rather than [bluebird promises](http://bluebirdjs.com). You will not need to make any changes unless you are using some of the additional methods found on bluebird promises that do not exist on native promises. 118 | 119 | 120 | ## 4.9.0 - 2020-08-19 121 | 122 | ### Changed 123 | 124 | * Added `letter_contact_block` to the responses for `getTemplateById`, `getTemplateByIdAndVersion` and `getAllTemplates`. 125 | 126 | ## 4.8.0 - 2020-06-18 127 | 128 | ### Changed 129 | 130 | * Add support for an optional `isCsv` parameter in the `prepareUpload` function. This fixes a bug when sending a CSV file by email. This ensures that the file is downloaded as a CSV rather than a TXT file. 131 | 132 | ## [4.7.3] - 2020-04-03 133 | 134 | ### Changed 135 | 136 | * Remove `__dirname` global call from api client 137 | 138 | ## [4.7.2] - 2020-01-31 139 | 140 | ### Changed 141 | 142 | * Add homepage to package.json 143 | 144 | ## [4.7.1] - 2020-01-27 145 | 146 | ### Changed 147 | 148 | * Refer to files, not documents, in error messages 149 | 150 | ## [4.7.0] - 2019-11-01 151 | 152 | ### Changed 153 | 154 | * Add `notifyClient.getPdfForLetterNotification(notificationId)` 155 | * Returns a Buffer with pdf data 156 | * Will raise a BadRequestError if the PDF is not available 157 | 158 | ## [4.6.0] - 2019-02-01 159 | 160 | ### Changed 161 | 162 | * Added an optional postage argument to `sendPrecompiledLetter` 163 | 164 | ## [4.5.2] - 2018-11-05 165 | 166 | ### Changed 167 | 168 | * Moved documentation to https://docs.notifications.service.gov.uk/node.html (generated from DOCUMENTATION.md) 169 | 170 | ## [4.5.1] - 2018-09-14 171 | 172 | ### Changed 173 | 174 | * Made formatting consistent across documentation. 175 | 176 | ## [4.5.0] - 2018-09-13 177 | 178 | ### Changed 179 | 180 | * Added a function to send precompiled letter through the client. 181 | * the new function uses a helper function to check if the file size is within the 5MB limit and to encode the file using base64. Then a POST request to our API is made with the file data and user reference. 182 | * instructions for the new functionality added to the documentation. 183 | 184 | ## [4.4.0] - 2018-09-05 185 | 186 | ### Changed 187 | 188 | * Added instructions for uploading a document to be linked to from an email notification. 189 | * Created a helper function `prepareUpload` as a part of this development. This function encodes the document that is to be uploaded with base64 and returns a dictionary with prepared document as a value (the way our API is prepared to receive it). It also checks if the provided file is not larger than 2MB. The function throws an error if the file is too big. 190 | 191 | ## [4.3.0] - 2018-09-04 192 | 193 | * Added `name` to the response for `getTemplateById()` and `getTemplateByIdAndVersion()` 194 | * These functions now return the name of the template as set in Notify 195 | 196 | ## [4.2.0] - 2018-07-24 197 | 198 | ### Changed 199 | 200 | * Added `created_by_name` to the response for `.getNotificationById()` and `.getNotifications()`: 201 | * If the notification was sent manually this will be the name of the sender. If the notification was sent through the API this will be `null`. 202 | 203 | ## [4.1.0] - 2017-11-23 204 | 205 | ### Changed 206 | 207 | * Added new method: 208 | * `getReceivedTexts` - get one page of text messages (250) per call 209 | 210 | ## [4.0.0] - 2017-11-07 211 | 212 | ### Changed 213 | 214 | * Updated `sendSms`, `sendEmail` and `sendLetter` to take an `options` object as a parameter. 215 | * `personalisation`, `reference`, `smsSenderId` and `emailReplyToId` now need to be passed to these functions inside `options`. 216 | * Removed the unused `Crypto` dependency 217 | 218 | ## [3.5.0] - 2017-11-01 219 | 220 | ### Changed 221 | 222 | * Updated `sendSms` method with optional argument: 223 | * `smsSenderId` - specify the identifier of the sms sender (optional) 224 | 225 | ## [3.4.0] - 2017-10-19 226 | 227 | ### Changed 228 | 229 | * Added new method: 230 | * `sendLetter` - send a letter 231 | 232 | ## [3.3.0] - 2017-10-13 233 | 234 | ### Changed 235 | 236 | * Updated `sendEmail` method with optional argument: 237 | * `emailReplyToId` - specify the identifier of the email reply-to address (optional) 238 | 239 | ## [3.2.0] - 2017-09-22 240 | 241 | ### Changed 242 | 243 | * Added new method: 244 | * `setProxy(proxyUrl)` - specify the URL of a proxy for the client to use (optional) 245 | 246 | ## [3.1.0] - 2017-05-10 247 | 248 | ### Changed 249 | 250 | * Added new methods for managing templates: 251 | * `getTemplateById` - retrieve a single template 252 | * `getTemplateByIdAndVersion` - retrieve a specific version for a desired template 253 | * `getAllTemplates` - retrieve all templates (can filter by type) 254 | * `previewTemplateById` - preview a template with personalisation applied 255 | 256 | * Update README to describe how to catch errors 257 | 258 | ## [3.0.0] - 2016-12-16 259 | 260 | ### Changed 261 | 262 | * Using v2 of the notification-api. 263 | 264 | * Update to `notifyClient.sendSms()`: 265 | * Added `reference`: an optional identifier you generate if you don’t want to use Notify’s `id`. It can be used to identify a single notification or a batch of notifications. 266 | * Updated method signature: 267 | 268 | ```javascript 269 | notifyClient.sendSms(templateId, phoneNumber, personalisation, reference); 270 | ``` 271 | * Where `personalisation` and `reference` can be `undefined`. 272 | 273 | * Update to `notifyClient.sendEmail()`: 274 | * Added `reference`: an optional identifier you generate if you don’t want to use Notify’s `id`. It can be used to identify a single notification or a batch of notifications. 275 | * Updated method signature: 276 | 277 | ```javascript 278 | notifyClient.sendEmail(templateId, emailAddress, personalisation, reference); 279 | ``` 280 | * Where `personalisation` and `reference` can be `undefined`. 281 | * `NotificationClient.getAllNotifications()` 282 | * Notifications can now be filtered by `reference` and `olderThanId`, see the README for details. 283 | * Updated method signature: 284 | 285 | ```javascript 286 | notifyClient.getNotifications(templateType, status, reference, olderThanId); 287 | ``` 288 | * Each one of these parameters can be `undefined` 289 | 290 | # Prior versions 291 | 292 | Changelog not recorded - please see pull requests on github. 293 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Pull requests welcome. 4 | 5 | ## Setting Up 6 | 7 | ### Docker container 8 | 9 | This app uses dependencies that are difficult to install locally. In order to make local development easy, we run app commands through a Docker container. Run the following to set this up: 10 | 11 | ```shell 12 | make bootstrap-with-docker 13 | ``` 14 | 15 | ### `environment.sh` 16 | 17 | In the root directory of the repo, run: 18 | 19 | ``` 20 | notify-pass credentials/client-integration-tests > environment.sh 21 | ``` 22 | 23 | Unless you're part of the GOV.UK Notify team, you won't be able to run this command or the Integration Tests. However, the file still needs to exist - run `touch environment.sh` instead. 24 | 25 | ## Tests 26 | 27 | There are unit and integration tests that can be run to test functionality of the client. 28 | 29 | ### Unit tests 30 | 31 | To run the unit tests: 32 | 33 | ``` 34 | make test-with-docker 35 | ``` 36 | 37 | ### Integration Tests 38 | 39 | To run the integration tests: 40 | 41 | ``` 42 | make integration-test-with-docker 43 | ``` 44 | 45 | ## Working on the client locally 46 | 47 | ``` 48 | npm install --save notifications-node-client 49 | ``` 50 | 51 | ## Testing JavaScript examples in Markdown 52 | 53 | We automatically test that the JavaScript in the documentation examples matches [our linting standards](https://gds-way.cloudapps.digital/manuals/programming-languages/nodejs/#source-formatting-and-linting). This also catches some issues that could stop an example from running when copied. 54 | 55 | You can fix issues automatically by running: 56 | 57 | ``` 58 | npm run test:markdown:standard -- --fix 59 | ``` 60 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ghcr.io/alphagov/notify/node:18-slim 2 | 3 | ENV DEBIAN_FRONTEND=noninteractive 4 | 5 | RUN \ 6 | echo "Install base packages" \ 7 | && apt-get update \ 8 | && apt-get install -y --no-install-recommends \ 9 | awscli \ 10 | make \ 11 | gnupg \ 12 | jq \ 13 | && echo "Clean up" \ 14 | && rm -rf /var/lib/apt/lists/* /tmp/* 15 | 16 | WORKDIR /var/project 17 | COPY . . 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Crown Copyright (Government Digital Service) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT_GOAL := help 2 | SHELL := /bin/bash 3 | 4 | .PHONY: help 5 | help: 6 | @cat $(MAKEFILE_LIST) | grep -E '^[a-zA-Z_-]+:.*?## .*$$' | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 7 | 8 | .PHONY: bootstrap 9 | bootstrap: ## Install build dependencies 10 | npm ci 11 | 12 | .PHONY: build 13 | build: bootstrap ## Build project (dummy task for CI) 14 | 15 | .PHONY: test 16 | test: ## Run tests 17 | npm test 18 | 19 | .PHONY: integration-test 20 | integration-test: ## Run integration tests 21 | npm test --integration 22 | 23 | .PHONY: bootstrap-with-docker 24 | bootstrap-with-docker: ## Prepare the Docker builder image 25 | docker build -t notifications-node-client . 26 | ./scripts/run_with_docker.sh make bootstrap 27 | 28 | .PHONY: test-with-docker 29 | test-with-docker: ## Run tests inside a Docker container 30 | ./scripts/run_with_docker.sh make test 31 | 32 | .PHONY: integration-test-with-docker 33 | integration-test-with-docker: ## Run integration tests inside a Docker container 34 | ./scripts/run_with_docker.sh make integration-test 35 | 36 | .PHONY: get-client-version 37 | get-client-version: ## Retrieve client version number from source code 38 | @node -p "require('./package.json').version" 39 | 40 | clean: 41 | rm -rf .cache venv 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GOV.UK Notify Node.js client 2 | 3 | Use this client to send emails, text messages and letters using the [GOV.UK Notify](https://www.notifications.service.gov.uk) API. 4 | 5 | Useful links: 6 | 7 | - [Documentation](https://docs.notifications.service.gov.uk/node.html) 8 | - [NPM package](https://www.npmjs.com/package/notifications-node-client) 9 | - [Changelog](https://github.com/alphagov/notifications-node-client/blob/main/CHANGELOG.md) 10 | - [Contributing to this client](https://github.com/alphagov/notifications-node-client/blob/main/CONTRIBUTING.md) 11 | -------------------------------------------------------------------------------- /client/api_client.js: -------------------------------------------------------------------------------- 1 | var defaultRestClient = require('axios').default, 2 | createGovukNotifyToken = require('../client/authentication.js'), 3 | notifyProductionAPI = 'https://api.notifications.service.gov.uk', 4 | version = require('../package.json').version; 5 | 6 | /** 7 | * @param urlBase 8 | * @param serviceId 9 | * @param apiKeyId 10 | * 11 | * @constructor 12 | */ 13 | function ApiClient() { 14 | 15 | this.proxy = null; 16 | this.restClient = defaultRestClient; 17 | 18 | if (arguments.length === 1) { 19 | this.urlBase = notifyProductionAPI; 20 | this.apiKeyId = arguments[0].substring(arguments[0].length - 36, arguments[0].length); 21 | this.serviceId = arguments[0].substring(arguments[0].length - 73, arguments[0].length - 37); 22 | } 23 | 24 | if (arguments.length === 2) { 25 | 26 | if (arguments[0].startsWith('http')) { 27 | this.urlBase = arguments[0]; 28 | this.apiKeyId = arguments[1].substring(arguments[1].length - 36, arguments[1].length); 29 | this.serviceId = arguments[1].substring(arguments[1].length - 73, arguments[1].length - 37); 30 | } else { 31 | this.urlBase = notifyProductionAPI; 32 | this.serviceId = arguments[0]; 33 | this.apiKeyId = arguments[1].substring(arguments[1].length - 36, arguments[1].length); 34 | } 35 | 36 | } 37 | 38 | if (arguments.length === 3) { 39 | this.urlBase = arguments[0]; 40 | this.serviceId = arguments[1]; 41 | this.apiKeyId = arguments[2].substring(arguments[2].length - 36, arguments[2].length); 42 | } 43 | 44 | } 45 | 46 | /** 47 | * 48 | * @param {string} requestMethod 49 | * @param {string} requestPath 50 | * @param {string} apiKeyId 51 | * @param {string} serviceId 52 | * 53 | * @returns {string} 54 | */ 55 | function createToken(requestMethod, requestPath, apiKeyId, serviceId) { 56 | return createGovukNotifyToken(requestMethod, requestPath, apiKeyId, serviceId); 57 | } 58 | 59 | Object.assign(ApiClient.prototype, { 60 | 61 | /** 62 | * 63 | * @param {string} path 64 | * 65 | * @returns {Promise} 66 | */ 67 | get: function(path, additionalOptions) { 68 | var options = { 69 | method: 'get', 70 | url: this.urlBase + path, 71 | headers: { 72 | 'Authorization': 'Bearer ' + createToken('GET', path, this.apiKeyId, this.serviceId), 73 | 'User-Agent': 'NOTIFY-API-NODE-CLIENT/' + version 74 | } 75 | }; 76 | Object.assign(options, additionalOptions) 77 | if(this.proxy !== null) options.proxy = this.proxy; 78 | 79 | return this.restClient(options); 80 | }, 81 | 82 | /** 83 | * 84 | * @param {string} path 85 | * @param {object} data 86 | * 87 | * @returns {Promise} 88 | */ 89 | post: function(path, data){ 90 | var options = { 91 | method: 'post', 92 | url: this.urlBase + path, 93 | data: data, 94 | headers: { 95 | 'Authorization': 'Bearer ' + createToken('GET', path, this.apiKeyId, this.serviceId), 96 | 'User-Agent': 'NOTIFY-API-NODE-CLIENT/' + version 97 | } 98 | }; 99 | 100 | if(this.proxy !== null) options.proxy = this.proxy; 101 | 102 | return this.restClient(options); 103 | }, 104 | 105 | /** 106 | * 107 | * @param {object} an axios proxy config 108 | */ 109 | setProxy: function(proxyConfig){ 110 | this.proxy = proxyConfig 111 | }, 112 | 113 | /** 114 | * 115 | * @param {object} an axios instance 116 | */ 117 | setClient: function(restClient){ 118 | this.restClient = restClient; 119 | } 120 | }); 121 | 122 | module.exports = ApiClient; 123 | -------------------------------------------------------------------------------- /client/authentication.js: -------------------------------------------------------------------------------- 1 | var jwt = require('jsonwebtoken'); 2 | 3 | 4 | function createGovukNotifyToken(request_method, request_path, secret, client_id) { 5 | 6 | return jwt.sign( 7 | { 8 | iss: client_id, 9 | iat: Math.round(Date.now() / 1000) 10 | }, 11 | secret, 12 | { 13 | header: {typ: "JWT", alg: "HS256"} 14 | } 15 | ); 16 | } 17 | 18 | module.exports = createGovukNotifyToken; 19 | -------------------------------------------------------------------------------- /client/notification.js: -------------------------------------------------------------------------------- 1 | var ApiClient = require('./api_client'); 2 | 3 | /** 4 | * 5 | * @param baseUrl 6 | * @param serviceId 7 | * @param apiKeyId 8 | * 9 | * @constructor 10 | */ 11 | function NotifyClient() { 12 | this.apiClient = new (Function.prototype.bind.apply( 13 | ApiClient, 14 | [null].concat(Array.prototype.slice.call(arguments)) 15 | )); 16 | } 17 | 18 | /** 19 | * 20 | * @param {String} type 21 | * @param {String} templateId 22 | * @param {String} to 23 | * @param {Object} personalisation 24 | * @param {String} reference 25 | * @param {String} replyToId 26 | * @param {String} oneClickUnsubscribeURL 27 | * 28 | * @returns {Object} 29 | */ 30 | function createNotificationPayload(type, templateId, to, personalisation, reference, replyToId, oneClickUnsubscribeURL) { 31 | 32 | var payload = { 33 | template_id: templateId 34 | }; 35 | 36 | if (type == 'email') { 37 | payload.email_address = to; 38 | } else if (type == 'sms') { 39 | payload.phone_number = to; 40 | } 41 | 42 | if (type == 'letter' || personalisation) { 43 | // personalisation mandatory for letters 44 | payload.personalisation = personalisation; 45 | } 46 | 47 | if (reference) { 48 | payload.reference = reference; 49 | } 50 | 51 | if (replyToId && type == 'email') { 52 | payload.email_reply_to_id = replyToId; 53 | } else if (replyToId && type == 'sms') { 54 | payload.sms_sender_id = replyToId; 55 | } 56 | 57 | if (oneClickUnsubscribeURL && type == 'email') { 58 | payload.one_click_unsubscribe_url = oneClickUnsubscribeURL; 59 | } 60 | 61 | return payload; 62 | } 63 | 64 | /** 65 | * 66 | * @param {String} templateType 67 | * @param {String} status 68 | * @param {String} reference 69 | * @param {String} olderThanId 70 | * 71 | * @returns {String} 72 | */ 73 | function buildGetAllNotificationsQuery(templateType, status, reference, olderThanId) { 74 | 75 | let payload = {} 76 | 77 | if (templateType) { 78 | payload.template_type = templateType; 79 | } 80 | 81 | if (status) { 82 | payload.status = status; 83 | } 84 | 85 | if (reference) { 86 | payload.reference = reference; 87 | } 88 | 89 | if (olderThanId) { 90 | payload.older_than = olderThanId; 91 | } 92 | 93 | return buildQueryStringFromDict(payload); 94 | } 95 | 96 | function buildQueryStringFromDict(dictionary) { 97 | var queryString = Object.keys(dictionary).map(function(key) { 98 | return [key, dictionary[key]].map(encodeURIComponent).join("="); 99 | }).join('&'); 100 | 101 | return queryString ? '?' + queryString : ''; 102 | } 103 | 104 | function checkOptionsKeys(allowedKeys, options) { 105 | let invalidKeys = Object.keys(options).filter((key_name) => 106 | !allowedKeys.includes(key_name) 107 | ); 108 | 109 | if (invalidKeys.length) { 110 | let err_msg = ( 111 | 'NotifyClient now uses an options configuration object. Options ' + JSON.stringify(invalidKeys) + 112 | ' not recognised. Please refer to the README.md for more information on method signatures.' 113 | ) 114 | return new Error(err_msg); 115 | } 116 | return null; 117 | } 118 | 119 | function _check_and_encode_file(file, size_limit) { 120 | if (file.length > size_limit * 1024 * 1024) { 121 | throw "File is larger than " + String(size_limit) + "MB."; 122 | } 123 | if (typeof(file) === 'string') { 124 | file = Buffer.from(file); 125 | } 126 | return file.toString('base64') 127 | } 128 | 129 | Object.assign(NotifyClient.prototype, { 130 | /** 131 | * Usage: 132 | * 133 | * notifyClient = new NotifyClient(urlBase, serviceId, apiKeyId); 134 | * 135 | * notifyClient.sendEmail(templateId, email, personalisation) 136 | * .then(function (response) { 137 | * //do stuff with response 138 | * }) 139 | * .catch(function (error) { 140 | * //deal with errors here 141 | * }); 142 | * 143 | * 144 | * @param {String} templateId 145 | * @param {String} emailAddress 146 | * @param {Object} options 147 | * 148 | * @returns {Promise} 149 | */ 150 | sendEmail: function (templateId, emailAddress, options) { 151 | options = options || {}; 152 | var err = checkOptionsKeys(['personalisation', 'reference', 'emailReplyToId', 'oneClickUnsubscribeURL'], options) 153 | if (err) { 154 | return Promise.reject(err); 155 | } 156 | var personalisation = options.personalisation || undefined, 157 | reference = options.reference || undefined, 158 | emailReplyToId = options.emailReplyToId || undefined, 159 | oneClickUnsubscribeURL = options.oneClickUnsubscribeURL || undefined; 160 | 161 | return this.apiClient.post('/v2/notifications/email', 162 | createNotificationPayload('email', templateId, emailAddress, personalisation, reference, emailReplyToId, oneClickUnsubscribeURL)); 163 | }, 164 | 165 | /** 166 | * 167 | * @param {String} templateId 168 | * @param {String} phoneNumber 169 | * @param {Object} options 170 | * 171 | * @returns {Promise} 172 | */ 173 | sendSms: function (templateId, phoneNumber, options) { 174 | options = options || {}; 175 | var err = checkOptionsKeys(['personalisation', 'reference', 'smsSenderId'], options) 176 | if (err) { 177 | return Promise.reject(err); 178 | } 179 | 180 | var personalisation = options.personalisation || undefined; 181 | var reference = options.reference || undefined; 182 | var smsSenderId = options.smsSenderId || undefined; 183 | 184 | return this.apiClient.post('/v2/notifications/sms', 185 | createNotificationPayload('sms', templateId, phoneNumber, personalisation, reference, smsSenderId)); 186 | }, 187 | 188 | /** 189 | * 190 | * @param {String} templateId 191 | * @param {Object} options 192 | * 193 | * @returns {Promise} 194 | */ 195 | sendLetter: function (templateId, options) { 196 | options = options || {}; 197 | var err = checkOptionsKeys(['personalisation', 'reference'], options) 198 | if (err) { 199 | return Promise.reject(err); 200 | } 201 | var personalisation = options.personalisation || undefined; 202 | var reference = options.reference || undefined; 203 | 204 | return this.apiClient.post('/v2/notifications/letter', 205 | createNotificationPayload('letter', templateId, undefined, personalisation, reference)); 206 | }, 207 | 208 | sendPrecompiledLetter: function(reference, pdf_file, postage) { 209 | var postage = postage || undefined 210 | var content = _check_and_encode_file(pdf_file, 5) 211 | var notification = { 212 | "reference": reference, 213 | "content": content 214 | } 215 | if (postage != undefined) { 216 | notification["postage"] = postage 217 | } 218 | return this.apiClient.post('/v2/notifications/letter', notification); 219 | }, 220 | 221 | /** 222 | * 223 | * @param {String} notificationId 224 | * 225 | * @returns {Promise} 226 | */ 227 | getNotificationById: function(notificationId) { 228 | return this.apiClient.get('/v2/notifications/' + notificationId); 229 | }, 230 | 231 | /** 232 | * 233 | * @param {String} templateType 234 | * @param {String} status 235 | * @param {String} reference 236 | * @param {String} olderThanId 237 | * 238 | * @returns {Promise} 239 | * 240 | */ 241 | getNotifications: function(templateType, status, reference, olderThanId) { 242 | return this.apiClient.get('/v2/notifications' + buildGetAllNotificationsQuery(templateType, status, reference, olderThanId)); 243 | }, 244 | 245 | /** 246 | * 247 | * @param {String} notificationId 248 | * 249 | * @returns {Promise} 250 | */ 251 | getPdfForLetterNotification: function(notificationId) { 252 | const url = '/v2/notifications/' + notificationId + '/pdf' 253 | 254 | // Unlike other requests, we expect a successful response as an arraybuffer and an error as JSON 255 | // Axios does not support flexible response types so we will need to handle the error case ourselves below 256 | return this.apiClient.get(url, { responseType: 'arraybuffer' }) 257 | .then(function(response) { 258 | var pdf = Buffer.from(response.data, "base64") 259 | return pdf 260 | }) 261 | .catch(function(error) { 262 | // If we receive an error, as the response is an arraybuffer rather than our usual JSON 263 | // we need to convert it to JSON to be read by the user 264 | string_of_error_body = new TextDecoder().decode(error.response.data); 265 | 266 | // Then we replace the error data with the JSON error rather than the arraybuffer of the error 267 | error.response.data = JSON.parse(string_of_error_body); 268 | 269 | // and rethrow to let the user handle the error 270 | throw error 271 | }); 272 | }, 273 | 274 | /** 275 | * 276 | * @param {String} templateId 277 | * 278 | * @returns {Promise} 279 | */ 280 | getTemplateById: function(templateId) { 281 | return this.apiClient.get('/v2/template/' + templateId); 282 | }, 283 | 284 | /** 285 | * 286 | * @param {String} templateId 287 | * @param {Integer} version 288 | * 289 | * @returns {Promise} 290 | */ 291 | getTemplateByIdAndVersion: function(templateId, version) { 292 | return this.apiClient.get('/v2/template/' + templateId + '/version/' + version); 293 | }, 294 | 295 | /** 296 | * 297 | * @param {String} type 298 | * 299 | * @returns {Promise} 300 | */ 301 | getAllTemplates: function(templateType) { 302 | let templateQuery = '' 303 | 304 | if (templateType) { 305 | templateQuery = '?type=' + templateType; 306 | } 307 | 308 | return this.apiClient.get('/v2/templates' + templateQuery); 309 | }, 310 | 311 | /** 312 | * 313 | * @param {String} templateId 314 | * @param {Dictionary} personalisation 315 | * 316 | * @returns {Promise} 317 | */ 318 | previewTemplateById: function(templateId, personalisation) { 319 | 320 | let payload = {} 321 | 322 | if (personalisation) { 323 | payload.personalisation = personalisation; 324 | } 325 | 326 | return this.apiClient.post('/v2/template/' + templateId + '/preview', payload); 327 | }, 328 | 329 | /** 330 | * 331 | * @param {String} olderThan 332 | * 333 | * @returns {Promise} 334 | */ 335 | getReceivedTexts: function(olderThan){ 336 | let queryString; 337 | 338 | if (olderThan) { 339 | queryString = '?older_than=' + olderThan; 340 | } else { 341 | queryString = ''; 342 | } 343 | 344 | return this.apiClient.get('/v2/received-text-messages' + queryString); 345 | }, 346 | 347 | /** 348 | * 349 | * @param {object} an axios proxy config 350 | */ 351 | setProxy: function(proxyConfig) { 352 | this.apiClient.setProxy(proxyConfig); 353 | }, 354 | 355 | /** 356 | * 357 | * @param {object} an axios instance 358 | */ 359 | setClient: function(client) { 360 | this.apiClient.setClient(client); 361 | }, 362 | 363 | /** 364 | * 365 | * @param {Buffer} fileData 366 | * @param {object} options 367 | * 368 | * @returns {Dictionary} 369 | */ 370 | prepareUpload: function(fileData, options) { 371 | let data = { 372 | file: _check_and_encode_file(fileData, 2), 373 | filename: null, 374 | confirm_email_before_download: null, 375 | retention_period: null, 376 | } 377 | 378 | if (options !== undefined) { 379 | data.filename = options.filename || null; 380 | data.confirm_email_before_download = options.confirmEmailBeforeDownload !== undefined ? options.confirmEmailBeforeDownload : null; 381 | data.retention_period = options.retentionPeriod || null; 382 | } 383 | 384 | return data; 385 | }, 386 | 387 | }); 388 | 389 | module.exports = { 390 | NotifyClient: NotifyClient 391 | }; 392 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var NotifyClient = require("./client/notification.js").NotifyClient; 2 | 3 | module.exports = { 4 | NotifyClient: NotifyClient 5 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "notifications-node-client", 3 | "version": "8.2.1", 4 | "homepage": "https://docs.notifications.service.gov.uk/node.html", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/alphagov/notifications-node-client.git" 8 | }, 9 | "description": "GOV.UK Notify Node.js client ", 10 | "main": "index.js", 11 | "scripts": { 12 | "test": "mocha \"spec/**/*.js\" && npm run test:markdown:standard", 13 | "test:markdown:standard": "standard-markdown DOCUMENTATION.md", 14 | "integration": "mocha spec/integration/test.js" 15 | }, 16 | "engines": { 17 | "npm": ">=6.14.13", 18 | "node": ">=14.17.3" 19 | }, 20 | "author": "GDS developers", 21 | "license": "MIT", 22 | "dependencies": { 23 | "axios": "^1.7.2", 24 | "jsonwebtoken": "^9.0.2" 25 | }, 26 | "devDependencies": { 27 | "chai": "4.3.4", 28 | "chai-as-promised": "7.1.1", 29 | "chai-bytes": "0.1.2", 30 | "chai-json-schema": "1.5.1", 31 | "jsonschema": "1.4.1", 32 | "mocha": "10.6.0", 33 | "mockdate": "3.0.5", 34 | "nock": "9.2.6", 35 | "optimist": "0.6.1", 36 | "sinon": "^18.0.0", 37 | "standard-markdown": "7.1.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /scripts/run_with_docker.sh: -------------------------------------------------------------------------------- 1 | DOCKER_IMAGE_NAME=notifications-node-client 2 | 3 | source environment.sh 4 | 5 | docker run \ 6 | --rm \ 7 | -v "`pwd`:/var/project" \ 8 | -e NOTIFY_API_URL=${NOTIFY_API_URL} \ 9 | -e API_KEY=${API_KEY} \ 10 | -e FUNCTIONAL_TEST_NUMBER=${FUNCTIONAL_TEST_NUMBER} \ 11 | -e FUNCTIONAL_TEST_EMAIL=${FUNCTIONAL_TEST_EMAIL} \ 12 | -e EMAIL_TEMPLATE_ID=${EMAIL_TEMPLATE_ID} \ 13 | -e SMS_TEMPLATE_ID=${SMS_TEMPLATE_ID} \ 14 | -e LETTER_TEMPLATE_ID=${LETTER_TEMPLATE_ID} \ 15 | -e EMAIL_REPLY_TO_ID=${EMAIL_REPLY_TO_ID} \ 16 | -e SMS_SENDER_ID=${SMS_SENDER_ID} \ 17 | -e API_SENDING_KEY=${API_SENDING_KEY} \ 18 | -e INBOUND_SMS_QUERY_KEY=${INBOUND_SMS_QUERY_KEY} \ 19 | -it \ 20 | ${DOCKER_IMAGE_NAME} \ 21 | ${@} 22 | -------------------------------------------------------------------------------- /scripts/test_send.js: -------------------------------------------------------------------------------- 1 | var argv = require('optimist') 2 | .usage('Usage: $0' + 3 | ' -b [baseUrl]' + 4 | ' -s [notify secret]' + 5 | ' -t [templateId]' + 6 | ' -d [destination (email address or phone number, not needed for letters]' + 7 | ' -p [personalisation (required for letter {"address_line_1": "mrs test", "address_line_2": "1 test street", "postcode": "SW1 1AA"})]' + 8 | ' -m [type (email, sms or letter, default email)]') 9 | .demand(['s', 't']) 10 | .argv, 11 | NotifyClient = require('../client/notification').NotifyClient, 12 | notifyClient, 13 | 14 | baseUrl = argv.b || 'https://api.notifications.service.gov.uk', 15 | secret = argv.s, 16 | templateId = argv.t, 17 | destination = argv.d, 18 | personalisation = argv.p ? JSON.parse(argv.p) : null, 19 | type = argv.m || 'email'; 20 | 21 | notifyClient = new NotifyClient(baseUrl, secret); 22 | 23 | switch(type) { 24 | 25 | case 'email': 26 | notifyClient.sendEmail(templateId, destination, personalisation) 27 | .then(function(response) { 28 | console.log('Notify response: ' + JSON.stringify(response)); 29 | }) 30 | .catch(function(error) { 31 | console.log('Error ' + error); 32 | }); 33 | break; 34 | 35 | case 'sms': 36 | notifyClient.sendSms(templateId, String(destination), personalisation) 37 | .then(function(response) { 38 | console.log('Notify response: ' + JSON.stringify(response)); 39 | }) 40 | .catch(function(error) { 41 | console.log('Error ' + error); 42 | }); 43 | break; 44 | 45 | case 'letter': 46 | notifyClient.sendLetter(templateId, personalisation) 47 | .then(function(response) { 48 | console.log('Notify response: ' + JSON.stringify(response)); 49 | }) 50 | .catch(function(error) { 51 | console.log('Error ' + error); 52 | }); 53 | break; 54 | 55 | default: 56 | console.log('Unrecognised notification type'); 57 | } 58 | -------------------------------------------------------------------------------- /spec/api_client.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect, 2 | MockDate = require('mockdate'), 3 | ApiClient = require('../client/api_client.js'), 4 | nock = require('nock'), 5 | createGovukNotifyToken = require('../client/authentication.js'), 6 | version = require('../package.json').version, 7 | axios = require('axios'), 8 | sinon = require('sinon'); 9 | 10 | describe('api client', function () { 11 | 12 | beforeEach(function() { 13 | MockDate.set(1234567890000); 14 | }); 15 | 16 | afterEach(function() { 17 | MockDate.reset(); 18 | }); 19 | 20 | it('should make a get request with correct headers', function (done) { 21 | 22 | var urlBase = 'https://api.notifications.service.gov.uk', 23 | path = '/email', 24 | body = { 25 | 'body': 'body text' 26 | }, 27 | serviceId = 'c745a8d8-b48a-4b0d-96e5-dbea0165ebd1', 28 | apiKeyId = '8b3aa916-ec82-434e-b0c5-d5d9b371d6a3'; 29 | 30 | [ 31 | new ApiClient(serviceId, apiKeyId), 32 | new ApiClient(urlBase, serviceId, apiKeyId), 33 | new ApiClient(urlBase, 'key_name' + '-' + serviceId + '-' + apiKeyId), 34 | new ApiClient('key_name' + ':' + serviceId + ':' + apiKeyId), 35 | ].forEach(function(client, index, clients) { 36 | 37 | nock(urlBase, { 38 | reqheaders: { 39 | 'Authorization': 'Bearer ' + createGovukNotifyToken('GET', path, apiKeyId, serviceId), 40 | 'User-Agent': 'NOTIFY-API-NODE-CLIENT/' + version 41 | } 42 | }) 43 | .get(path) 44 | .reply(200, body); 45 | 46 | client.get(path) 47 | .then(function (response) { 48 | expect(response.data).to.deep.equal(body); 49 | if (index == clients.length - 1) done(); 50 | }); 51 | 52 | }); 53 | 54 | }); 55 | 56 | it('should make a post request with correct headers', function (done) { 57 | 58 | var urlBase = 'http://localhost', 59 | path = '/email', 60 | data = { 61 | 'data': 'qwjjs' 62 | }, 63 | serviceId = 123, 64 | apiKeyId = 'SECRET', 65 | apiClient = new ApiClient(urlBase, serviceId, apiKeyId); 66 | 67 | nock(urlBase, { 68 | reqheaders: { 69 | 'Authorization': 'Bearer ' + createGovukNotifyToken('POST', path, apiKeyId, serviceId), 70 | 'User-Agent': 'NOTIFY-API-NODE-CLIENT/' + version 71 | } 72 | }) 73 | .post(path, data) 74 | .reply(200, {"hooray": "bkbbk"}); 75 | 76 | apiClient = new ApiClient(urlBase, serviceId, apiKeyId); 77 | apiClient.post(path, data) 78 | .then(function (response) { 79 | expect(response.status).to.equal(200); 80 | done(); 81 | }); 82 | }); 83 | 84 | it('should direct get requests through the proxy when set', function (done) { 85 | var urlBase = 'https://api.notifications.service.gov.uk', 86 | proxyConfig = { host: 'addressofmyproxy.test', protocol: 'http'}, 87 | path = '/email', 88 | apiClient = new ApiClient(urlBase, 'apiKey'); 89 | 90 | nock("http://" + proxyConfig.host) 91 | .get(urlBase + path) 92 | .reply(200, 'test'); 93 | 94 | apiClient.setProxy(proxyConfig); 95 | apiClient.get(path) 96 | .then(function (response) { 97 | expect(response.status).to.equal(200); 98 | expect(response.config.proxy).to.eql(proxyConfig); 99 | done(); 100 | }); 101 | }); 102 | 103 | it('should direct post requests through the proxy when set', function (done) { 104 | var urlBase = 'https://api.notifications.service.gov.uk', 105 | proxyConfig = { host: 'addressofmyproxy.test', protocol: 'http'}, 106 | path = '/email', 107 | apiClient = new ApiClient(urlBase, 'apiKey'); 108 | 109 | nock("http://" + proxyConfig.host) 110 | .post(urlBase + path) 111 | .reply(200, 'test'); 112 | 113 | apiClient.setProxy(proxyConfig); 114 | apiClient.post(path) 115 | .then(function (response) { 116 | expect(response.status).to.equal(200); 117 | expect(response.config.proxy).to.eql(proxyConfig); 118 | done(); 119 | }); 120 | }); 121 | 122 | it('should use the custom Axios client when set', function (done) { 123 | var urlBase = 'https://api.notifications.service.gov.uk', 124 | path = '/email', 125 | body = { 126 | 'body': 'body text' 127 | }, 128 | serviceId = 'c745a8d8-b48a-4b0d-96e5-dbea0165ebd1', 129 | apiKeyId = '8b3aa916-ec82-434e-b0c5-d5d9b371d6a3'; 130 | 131 | var customClientStub = sinon.stub().resolves({ data: body }); 132 | 133 | var apiClient = new ApiClient(serviceId, apiKeyId); 134 | apiClient.setClient(customClientStub); 135 | 136 | nock(urlBase, { 137 | reqheaders: { 138 | 'Authorization': 'Bearer ' + createGovukNotifyToken('GET', path, apiKeyId, serviceId), 139 | 'User-Agent': 'NOTIFY-API-NODE-CLIENT/' + version 140 | } 141 | }) 142 | .get(path) 143 | .reply(200, body); 144 | 145 | apiClient.get(path) 146 | .then(function (response) { 147 | expect(response.data).to.deep.equal(body); 148 | expect(customClientStub.calledOnce).to.be.true; 149 | expect(customClientStub.args[0][0].url).to.equal(urlBase + path); 150 | expect(customClientStub.args[0][0].headers['Authorization']).to.equal('Bearer ' + createGovukNotifyToken('GET', path, apiKeyId, serviceId)); 151 | expect(customClientStub.args[0][0].headers['User-Agent']).to.equal('NOTIFY-API-NODE-CLIENT/' + version); 152 | done(); 153 | }) 154 | .catch(done); 155 | }); 156 | }); 157 | -------------------------------------------------------------------------------- /spec/authentication.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect, 2 | MockDate = require('mockdate'), 3 | jwt = require('jsonwebtoken'), 4 | createGovukNotifyToken = require('../client/authentication.js'); 5 | 6 | 7 | describe('Authentication', function() { 8 | 9 | beforeEach(function() { 10 | MockDate.set(1234567890000); 11 | }); 12 | 13 | afterEach(function() { 14 | MockDate.reset(); 15 | }); 16 | 17 | describe('tokens', function() { 18 | 19 | it('can be generated and decoded', function() { 20 | 21 | var token = createGovukNotifyToken("POST", "/v2/notifications/sms", "SECRET", 123), 22 | decoded = jwt.verify(token, 'SECRET'); 23 | 24 | expect(token).to.equal('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOjEyMywiaWF0IjoxMjM0NTY3ODkwfQ.18aBKSLffjbX_TLmosB_qYgW9EkWIQpBgWy7GpiKg6o'); 25 | expect(decoded.iss).to.equal(123); 26 | expect(decoded.iat).to.equal(1234567890); 27 | 28 | }); 29 | 30 | }); 31 | 32 | }); 33 | -------------------------------------------------------------------------------- /spec/integration/schemas/v1/GET_notifications_return.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "description": "GET notification return schema - for sms notifications", 4 | "type" : "object", 5 | "properties": { 6 | "notifications": { 7 | "type": "array", 8 | "items": { 9 | "oneOf": [ 10 | {"$ref": "sms_notification.json"}, 11 | {"$ref": "email_notification.json"} 12 | ] 13 | } 14 | }, 15 | "links": { 16 | "type": "object", 17 | "properties" : { 18 | "prev" : { 19 | "type" : "string" 20 | }, 21 | "next" : { 22 | "type" : "string" 23 | }, 24 | "last": { 25 | "type" : "string" 26 | } 27 | }, 28 | "additionalProperties": false 29 | }, 30 | "page_size": {"type": "number"}, 31 | "total": {"type": "number"} 32 | }, 33 | "additionalProperties": false, 34 | "required": [ 35 | "notifications", "links", "page_size", "total" 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /spec/integration/schemas/v1/POST_notification_return_email.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "description": "POST notification return schema - for email notifications", 4 | "type" : "object", 5 | "properties": { 6 | "data": { 7 | "type": "object", 8 | "properties": { 9 | "notification": { 10 | "type": "object", 11 | "properties": { 12 | "id": {"$ref": "definitions.json#/uuid"} 13 | }, 14 | "additionalProperties": false, 15 | "required": ["id"] 16 | }, 17 | "body": {"type": "string"}, 18 | "template_version": {"type": "number"}, 19 | "subject": {"type": "string"} 20 | }, 21 | "additionalProperties": false, 22 | "required": ["notification", "body", "template_version", "subject"] 23 | } 24 | }, 25 | "additionalProperties": false, 26 | "required": ["data"] 27 | } 28 | -------------------------------------------------------------------------------- /spec/integration/schemas/v1/POST_notification_return_sms.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "description": "POST notification return schema - for sms notifications", 4 | "type" : "object", 5 | "properties": { 6 | "data": { 7 | "type": "object", 8 | "properties": { 9 | "notification": { 10 | "type": "object", 11 | "properties": { 12 | "id": {"$ref": "definitions.json#/uuid"} 13 | }, 14 | "additionalProperties": false, 15 | "required": ["id"] 16 | }, 17 | "body": {"type": "string"}, 18 | "template_version": {"type": "number"} 19 | }, 20 | "additionalProperties": false, 21 | "required": ["notification", "body", "template_version"] 22 | } 23 | }, 24 | "additionalProperties": false, 25 | "required": ["data"] 26 | } 27 | -------------------------------------------------------------------------------- /spec/integration/schemas/v1/definitions.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "description": "Common definitions - usage example: {'$ref': 'definitions.json#/uuid'} (swap quotes for double quotes)", 4 | "uuid": { 5 | "type": "string", 6 | "pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$" 7 | }, 8 | "datetime": { 9 | "type": "string", 10 | "format": "date-time" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /spec/integration/schemas/v1/email_notification.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Single email notification schema - as returned by GET /notification and GET /notification/{}", 3 | "type": "object", 4 | "properties": { 5 | "id": {"$ref": "definitions.json#/uuid"}, 6 | "to": {"type": "string", "format": "email"}, 7 | "job_row_number": {"oneOf":[ 8 | {"type": "number"}, 9 | {"type": "null"} 10 | ]}, 11 | "template_version": {"type": "number"}, 12 | "billable_units": {"type": "number"}, 13 | "notification_type": { 14 | "type": "string", 15 | "enum": ["email"] 16 | }, 17 | "created_at": {"$ref": "definitions.json#/datetime"}, 18 | "sent_at": {"oneOf":[ 19 | {"$ref": "definitions.json#/datetime"}, 20 | {"type": "null"} 21 | ]}, 22 | "sent_by": {"oneOf":[ 23 | {"type": "string"}, 24 | {"type": "null"} 25 | ]}, 26 | "updated_at": {"oneOf":[ 27 | {"$ref": "definitions.json#/datetime"}, 28 | {"type": "null"} 29 | ]}, 30 | "status": { 31 | "type": "string", 32 | "enum": [ 33 | "created", 34 | "sending", 35 | "delivered", 36 | "pending", 37 | "failed", 38 | "technical-failure", 39 | "temporary-failure", 40 | "permanent-failure" 41 | ] 42 | }, 43 | "reference": {"oneOf":[ 44 | {"type": "string"}, 45 | {"type": "null"} 46 | ]}, 47 | "template": { 48 | "type": "object", 49 | "properties": { 50 | "id": {"$ref": "definitions.json#/uuid"}, 51 | "name": {"type": "string"}, 52 | "template_type": { 53 | "type": "string", 54 | "enum": ["email"] 55 | }, 56 | "version": {"type": "number"} 57 | }, 58 | "additionalProperties": false, 59 | "required": ["id", "name", "template_type", "version"] 60 | }, 61 | "service": {"$ref": "definitions.json#/uuid"}, 62 | "job": { 63 | "oneOf": [ 64 | { 65 | "type": "object", 66 | "properties": { 67 | "id": {"$ref": "definitions.json#/uuid"}, 68 | "original_file_name": {"type": "string"} 69 | }, 70 | "additionalProperties": false, 71 | "required": ["id", "original_file_name"] 72 | }, 73 | {"type": "null"} 74 | ] 75 | }, 76 | "api_key": {"oneOf":[ 77 | {"$ref": "definitions.json#/uuid"}, 78 | {"type": "null"} 79 | ]}, 80 | "body": {"type": "string"}, 81 | "content_char_count": {"type": "null"}, 82 | "subject": {"type": "string"} 83 | }, 84 | "additionalProperties": false, 85 | "required": [ 86 | "id", 87 | "to", 88 | "job_row_number", 89 | "template_version", 90 | "billable_units", 91 | "notification_type", 92 | "created_at", 93 | "sent_at", 94 | "sent_by", 95 | "updated_at", 96 | "status", 97 | "reference", 98 | "template", 99 | "service", 100 | "job", 101 | "api_key", 102 | "body", 103 | "content_char_count", 104 | "subject" 105 | ] 106 | } 107 | -------------------------------------------------------------------------------- /spec/integration/schemas/v1/sms_notification.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Single sms notification schema - as returned by GET /notification and GET /notification/{}", 3 | "type": "object", 4 | "properties": { 5 | "id": {"$ref": "definitions.json#/uuid"}, 6 | "to": {"type": "string"}, 7 | "job_row_number": {"oneOf":[ 8 | {"type": "number"}, 9 | {"type": "null"} 10 | ]}, 11 | "template_version": {"type": "number"}, 12 | "billable_units": {"type": "number"}, 13 | "notification_type": { 14 | "type": "string", 15 | "enum": ["sms"] 16 | }, 17 | "created_at": {"$ref": "definitions.json#/datetime"}, 18 | "sent_at": {"oneOf":[ 19 | {"$ref": "definitions.json#/datetime"}, 20 | {"type": "null"} 21 | ]}, 22 | "sent_by": {"oneOf":[ 23 | {"type": "string"}, 24 | {"type": "null"} 25 | ]}, 26 | "updated_at": {"oneOf":[ 27 | {"$ref": "definitions.json#/datetime"}, 28 | {"type": "null"} 29 | ]}, 30 | "status": { 31 | "type": "string", 32 | "enum": [ 33 | "created", 34 | "sending", 35 | "delivered", 36 | "pending", 37 | "failed", 38 | "technical-failure", 39 | "temporary-failure", 40 | "permanent-failure" 41 | ] 42 | }, 43 | "reference": {"oneOf":[ 44 | {"type": "string"}, 45 | {"type": "null"} 46 | ]}, 47 | "template": { 48 | "type": "object", 49 | "properties": { 50 | "id": {"$ref": "definitions.json#/uuid"}, 51 | "name": {"type": "string"}, 52 | "template_type": { 53 | "type": "string", 54 | "enum": ["sms"] 55 | }, 56 | "version": {"type": "number"} 57 | }, 58 | "additionalProperties": false, 59 | "required": ["id", "name", "template_type", "version"] 60 | }, 61 | "service": {"$ref": "definitions.json#/uuid"}, 62 | "job": { 63 | "oneOf": [ 64 | { 65 | "type": "object", 66 | "properties": { 67 | "id": {"$ref": "definitions.json#/uuid"}, 68 | "original_file_name": {"type": "string"} 69 | }, 70 | "additionalProperties": false, 71 | "required": ["id", "original_file_name"] 72 | }, 73 | {"type": "null"} 74 | ] 75 | }, 76 | "api_key": {"oneOf":[ 77 | {"$ref": "definitions.json#/uuid"}, 78 | {"type": "null"} 79 | ]}, 80 | "body": {"type": "string"}, 81 | "content_char_count": {"type": "number"} 82 | }, 83 | "additionalProperties": false, 84 | "required": [ 85 | "id", 86 | "to", 87 | "job_row_number", 88 | "template_version", 89 | "billable_units", 90 | "notification_type", 91 | "created_at", 92 | "sent_at", 93 | "sent_by", 94 | "updated_at", 95 | "status", 96 | "reference", 97 | "template", 98 | "service", 99 | "job", 100 | "api_key", 101 | "body", 102 | "content_char_count" 103 | ] 104 | } 105 | -------------------------------------------------------------------------------- /spec/integration/schemas/v2/GET_notification_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/schema#", 3 | "description": "GET notification response schema", 4 | "type": "object", 5 | "title": "response v2/notification", 6 | "properties": { 7 | "id": {"$ref": "definitions.json#/uuid"}, 8 | "reference": {"type": ["string", "null"]}, 9 | "email_address": {"type": ["string", "null"]}, 10 | "phone_number": {"type": ["string", "null"]}, 11 | "line_1": {"type": ["string", "null"]}, 12 | "line_2": {"type": ["string", "null"]}, 13 | "line_3": {"type": ["string", "null"]}, 14 | "line_4": {"type": ["string", "null"]}, 15 | "line_5": {"type": ["string", "null"]}, 16 | "line_6": {"type": ["string", "null"]}, 17 | "postcode": {"type": ["string", "null"]}, 18 | "postage": {"type": ["string", "null"]}, 19 | "type": {"enum": ["sms", "letter", "email"]}, 20 | "status": {"type": "string"}, 21 | "template": {"$ref": "definitions.json#/template"}, 22 | "body": {"type": "string"}, 23 | "subject": {"type": ["string", "null"]}, 24 | "created_at": {"type": "string"}, 25 | "created_by_name": {"type": ["string", "null"]}, 26 | "sent_at": {"type": ["string", "null"]}, 27 | "completed_at": {"type": ["string", "null"]}, 28 | "scheduled_for": {"oneOf":[ 29 | {"$ref": "definitions.json#/datetime"}, 30 | {"type": "null"} 31 | ]}, 32 | "cost_in_pounds": {"type": ["number", "null"]}, 33 | "is_cost_data_ready": {"type": "boolean"}, 34 | "cost_details": { 35 | "type": "object", 36 | "properties": { 37 | "billable_sms_fragments": {"type": ["integer", "null"]}, 38 | "international_rate_multiplier": {"type": ["number", "null"]}, 39 | "sms_rate": {"type": ["number", "null"]}, 40 | "billable_sheets_of_paper": {"type": ["integer", "null"]}, 41 | "postage": {"type": ["string", "null"]} 42 | } 43 | } 44 | }, 45 | "required": [ 46 | "id", "reference", "email_address", "phone_number", 47 | "line_1", "line_2", "line_3", "line_4", "line_5", "line_6", "postcode", 48 | "type", "status", "template", "created_at", "created_by_name", "sent_at", "completed_at" 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /spec/integration/schemas/v2/GET_notifications_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/schema#", 3 | "description": "GET list of notifications response schema", 4 | "type": "object", 5 | "properties": { 6 | "notifications": { 7 | "type": "array", 8 | "items": { 9 | "type": "object", 10 | "$ref": "notification.json" 11 | } 12 | }, 13 | "links": { 14 | "type": "object", 15 | "properties": { 16 | "current": { 17 | "type": "string" 18 | }, 19 | "next": { 20 | "type": "string" 21 | } 22 | }, 23 | "additionalProperties": false, 24 | "required": ["current"] 25 | } 26 | }, 27 | "additionalProperties": false, 28 | "required": ["notifications", "links"] 29 | } 30 | -------------------------------------------------------------------------------- /spec/integration/schemas/v2/GET_received_text_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "description": "GET inbound sms schema response", 4 | "type": "object", 5 | "title": "GET response v2/inbound_sms", 6 | "properties": { 7 | "user_number": {"type": "string"}, 8 | "created_at": { 9 | "format": "date-time", 10 | "type": "string", 11 | "description": "Date+time created at" 12 | }, 13 | "service_id": {"$ref": "definitions.json#/uuid"}, 14 | "id": {"$ref": "definitions.json#/uuid"}, 15 | "notify_number": {"type": "string"}, 16 | "content": {"type": "string"} 17 | }, 18 | "required": [ 19 | "id", "user_number", "created_at", "service_id", 20 | "notify_number", "content" 21 | ], 22 | "additionalProperties": false 23 | } 24 | -------------------------------------------------------------------------------- /spec/integration/schemas/v2/GET_received_texts_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "description": "GET list of inbound sms response schema", 4 | "type": "object", 5 | "properties": { 6 | "received_text_messages": { 7 | "type": "array", 8 | "items": { 9 | "type": "object", 10 | "$ref": "receivedText.json" 11 | } 12 | }, 13 | "links": { 14 | "type": "object", 15 | "properties": { 16 | "current": { 17 | "type": "string" 18 | }, 19 | "next": { 20 | "type": "string" 21 | } 22 | }, 23 | "additionalProperties": false, 24 | "required": ["current"] 25 | } 26 | }, 27 | "required": ["received_text_messages", "links"], 28 | "additionalProperties": false 29 | } 30 | -------------------------------------------------------------------------------- /spec/integration/schemas/v2/GET_template_by_id.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "description": "GET template by id schema response", 4 | "type": "object", 5 | "title": "reponse v2/template", 6 | "properties": { 7 | "id": {"$ref": "definitions.json#/uuid"}, 8 | "name": {"type": "string"}, 9 | "type": {"enum": ["sms", "email", "letter"] }, 10 | "created_at": { 11 | "format": "date-time", 12 | "type": "string", 13 | "description": "Date+time created" 14 | }, 15 | "updated_at": { 16 | "format": "date-time", 17 | "type": ["string", "null"], 18 | "description": "Date+time updated" 19 | }, 20 | "created_by": {"type": "string"}, 21 | "version": {"type": "integer"}, 22 | "body": {"type": "string"}, 23 | "subject": {"type": ["string", "null"]}, 24 | "letter_contact_block": {"type": ["string", "null"]} 25 | }, 26 | "required": [ 27 | "id", "name", "type", "created_at", "updated_at", "version", "created_by", 28 | "body", "letter_contact_block" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /spec/integration/schemas/v2/GET_templates_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "description": "GET response schema when getting all templates", 4 | "type": "object", 5 | "properties": { 6 | "templates": { 7 | "type": "array", 8 | "items": { 9 | "type": "object", 10 | "$ref": "definitions.json#/template" 11 | } 12 | } 13 | }, 14 | "required": ["templates"] 15 | } 16 | -------------------------------------------------------------------------------- /spec/integration/schemas/v2/POST_notification_email_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/schema#", 3 | "description": "POST notification email response schema", 4 | "type" : "object", 5 | "properties": { 6 | "id": {"$ref": "definitions.json#/uuid"}, 7 | "reference": {"type": ["string", "null"]}, 8 | "content": {"$ref": "definitions.json#/email_content"}, 9 | "uri": {"type": "string"}, 10 | "template": {"$ref": "definitions.json#/template"}, 11 | "scheduled_for": {"oneOf":[ 12 | {"$ref": "definitions.json#/datetime"}, 13 | {"type": "null"} 14 | ]} 15 | }, 16 | "additionalProperties": false, 17 | "required": ["id", "content", "uri", "template"] 18 | } 19 | -------------------------------------------------------------------------------- /spec/integration/schemas/v2/POST_notification_letter_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/schema#", 3 | "description": "POST notification letter response schema", 4 | "type" : "object", 5 | "properties": { 6 | "id": {"$ref": "definitions.json#/uuid"}, 7 | "reference": {"type": ["string", "null"]}, 8 | "postage": {"type": "string"}, 9 | "content": {"$ref": "definitions.json#/letter_content"}, 10 | "uri": {"type": "string"}, 11 | "template": {"$ref": "definitions.json#/template"}, 12 | "scheduled_for": {"oneOf":[ 13 | {"$ref": "definitions.json#/datetime"}, 14 | {"type": "null"} 15 | ]} 16 | }, 17 | "additionalProperties": false, 18 | "required": ["id", "content", "uri", "template"] 19 | } 20 | -------------------------------------------------------------------------------- /spec/integration/schemas/v2/POST_notification_precompiled_letter_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/schema#", 3 | "description": "POST notification precompiled letter response schema", 4 | "type" : "object", 5 | "properties": { 6 | "id": {"$ref": "definitions.json#/uuid"}, 7 | "reference": {"type": ["string", "null"]}, 8 | "postage": {"type": "string"}, 9 | "content": {"$ref": "definitions.json#/letter_content"}, 10 | "uri": {"type": "string"}, 11 | "template": {"$ref": "definitions.json#/template"}, 12 | "scheduled_for": {"oneOf":[ 13 | {"$ref": "definitions.json#/datetime"}, 14 | {"type": "null"} 15 | ]} 16 | }, 17 | "additionalProperties": false, 18 | "required": ["id", "postage"] 19 | } 20 | -------------------------------------------------------------------------------- /spec/integration/schemas/v2/POST_notification_sms_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/schema#", 3 | "description": "POST notification sms response schema", 4 | "type" : "object", 5 | "properties": { 6 | "id": {"$ref": "definitions.json#/uuid"}, 7 | "reference": {"type": ["string", "null"]}, 8 | "content": {"$ref": "definitions.json#/sms_content"}, 9 | "uri": {"type": "string"}, 10 | "template": {"$ref": "definitions.json#/template"}, 11 | "scheduled_for": {"oneOf":[ 12 | {"$ref": "definitions.json#/datetime"}, 13 | {"type": "null"} 14 | ]} 15 | }, 16 | "additionalProperties": false, 17 | "required": ["id", "content", "uri", "template"] 18 | } 19 | -------------------------------------------------------------------------------- /spec/integration/schemas/v2/POST_template_preview.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "description": "POST template preview schema response", 4 | "type": "object", 5 | "title": "reponse v2/template/{id}/preview", 6 | "properties": { 7 | "id": {"$ref": "definitions.json#/uuid"}, 8 | "type": {"enum": ["sms", "email", "letter"] }, 9 | "version": {"type": "integer"}, 10 | "body": {"type": "string"}, 11 | "subject": {"type": ["string", "null"]} 12 | }, 13 | "required": ["id", "type", "version", "body"] 14 | } 15 | -------------------------------------------------------------------------------- /spec/integration/schemas/v2/definitions.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/schema#", 3 | "description": "Common definitions - usage example: {'$ref': 'definitions.json#/uuid'} (swap quotes for double quotes)", 4 | "uuid": { 5 | "type": "string", 6 | "pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$" 7 | }, 8 | "datetime": { 9 | "type": "string", 10 | "format": "date-time" 11 | }, 12 | "template": { 13 | "type": "object", 14 | "title": "notification content", 15 | "properties": { 16 | "id": {"$ref": "#/uuid"}, 17 | "version": {"type": "integer"}, 18 | "uri": {"type": "string"} 19 | }, 20 | "required": ["id", "version"] 21 | }, 22 | "email_content": { 23 | "type": "object", 24 | "title": "notification email content", 25 | "properties": { 26 | "body": {"type": "string"}, 27 | "from_email": {"type": "string", "format": "email_address"}, 28 | "subject": {"type": "string"}, 29 | "one_click_unsubscribe_url": {"type": ["string", "null"], "format": "uri"} 30 | }, 31 | "required": ["body", "from_email", "subject"] 32 | }, 33 | "sms_content": { 34 | "type": "object", 35 | "title": "notification sms content", 36 | "properties": { 37 | "body": {"type": "string"}, 38 | "from_number": {"type": "string"} 39 | }, 40 | "required": ["body", "from_number"] 41 | }, 42 | "letter_content": { 43 | "type": "object", 44 | "title": "notification letter content", 45 | "properties": { 46 | "body": {"type": "string"}, 47 | "subject": {"type": "string"} 48 | }, 49 | "required": ["body", "subject"] 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /spec/integration/test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const NotifyClient = require('../../client/notification.js').NotifyClient; 3 | 4 | const chai = require('chai'); 5 | const chaiAsPromised = require('chai-as-promised'); 6 | const chaiJsonSchema = require('chai-json-schema'); 7 | const chaiBytes = require('chai-bytes'); 8 | chai.use(chaiAsPromised); 9 | chai.use(chaiJsonSchema); 10 | chai.use(chaiBytes); 11 | 12 | const should = chai.should(); 13 | const expect = chai.expect; 14 | 15 | // will not run unless flag provided `npm test --integration` 16 | const describer = process.env.npm_config_integration ? describe.only : describe.skip; 17 | 18 | function make_random_id() { 19 | var text = ""; 20 | var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 21 | 22 | for (var i = 0; i < 5; i++) 23 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 24 | 25 | return text; 26 | } 27 | 28 | describer('notification api with a live service', function () { 29 | // default is 2000 (ms) - api is sometimes slower than this :( 30 | this.timeout(120000) 31 | 32 | let notifyClient; 33 | let teamNotifyClient; 34 | let emailNotificationId; 35 | let smsNotificationId; 36 | let letterNotificationId; 37 | const personalisation = { name: 'Foo' }; 38 | const clientRef = 'client-ref'; 39 | const oneClickUnsubscribeURL = 'https://www.example.com'; 40 | const email = process.env.FUNCTIONAL_TEST_EMAIL; 41 | const phoneNumber = process.env.FUNCTIONAL_TEST_NUMBER; 42 | const letterContact = { 43 | address_line_1: make_random_id(), 44 | address_line_2: 'Foo', 45 | postcode: 'SW1 1AA', 46 | }; 47 | const smsTemplateId = process.env.SMS_TEMPLATE_ID; 48 | const smsSenderId = process.env.SMS_SENDER_ID || undefined; 49 | const emailTemplateId = process.env.EMAIL_TEMPLATE_ID; 50 | const emailReplyToId = process.env.EMAIL_REPLY_TO_ID || undefined; 51 | const letterTemplateId = process.env.LETTER_TEMPLATE_ID; 52 | 53 | beforeEach(() => { 54 | 55 | const urlBase = process.env.NOTIFY_API_URL; 56 | const apiKeyId = process.env.API_KEY 57 | const inboundSmsKeyId = process.env.INBOUND_SMS_QUERY_KEY; 58 | const teamApiKeyId = process.env.API_SENDING_KEY; 59 | notifyClient = new NotifyClient(urlBase, apiKeyId); 60 | teamNotifyClient = new NotifyClient(urlBase, teamApiKeyId); 61 | receivedTextClient = new NotifyClient(urlBase, inboundSmsKeyId); 62 | var definitions_json = require('./schemas/v2/definitions.json'); 63 | chai.tv4.addSchema('definitions.json', definitions_json); 64 | 65 | }); 66 | 67 | describe('notifications', () => { 68 | it('send email notification', () => { 69 | var postEmailNotificationResponseJson = require('./schemas/v2/POST_notification_email_response.json'), 70 | options = {personalisation: personalisation, reference: clientRef}; 71 | 72 | return notifyClient.sendEmail(emailTemplateId, email, options).then((response) => { 73 | response.status.should.equal(201); 74 | expect(response.data).to.be.jsonSchema(postEmailNotificationResponseJson); 75 | response.data.content.body.should.equal('Hello Foo\r\n\r\nFunctional test help make our world a better place'); 76 | response.data.content.subject.should.equal('Functional Tests are good'); 77 | response.data.reference.should.equal(clientRef); 78 | emailNotificationId = response.data.id; 79 | }) 80 | }); 81 | 82 | it('send email notification with email_reply_to_id', () => { 83 | var postEmailNotificationResponseJson = require('./schemas/v2/POST_notification_email_response.json'), 84 | options = {personalisation: personalisation, reference: clientRef, emailReplyToId: emailReplyToId}; 85 | 86 | should.exist(emailReplyToId); 87 | return notifyClient.sendEmail(emailTemplateId, email, options).then((response) => { 88 | response.status.should.equal(201); 89 | expect(response.data).to.be.jsonSchema(postEmailNotificationResponseJson); 90 | response.data.content.body.should.equal('Hello Foo\r\n\r\nFunctional test help make our world a better place'); 91 | response.data.content.subject.should.equal('Functional Tests are good'); 92 | should.equal(response.data.content.one_click_unsubscribe_url, null); 93 | response.data.reference.should.equal(clientRef); 94 | emailNotificationId = response.data.id; 95 | }) 96 | }); 97 | 98 | it('send email notification with unsubscribe link', () => { 99 | var postEmailNotificationResponseJson = require('./schemas/v2/POST_notification_email_response.json'), 100 | options = {personalisation: personalisation, reference: clientRef, oneClickUnsubscribeURL: oneClickUnsubscribeURL}; 101 | 102 | return notifyClient.sendEmail(emailTemplateId, email, options).then((response) => { 103 | response.status.should.equal(201); 104 | expect(response.data).to.be.jsonSchema(postEmailNotificationResponseJson); 105 | response.data.content.body.should.equal('Hello Foo\r\n\r\nFunctional test help make our world a better place'); 106 | response.data.content.subject.should.equal('Functional Tests are good'); 107 | response.data.content.one_click_unsubscribe_url.should.equal(oneClickUnsubscribeURL); 108 | response.data.reference.should.equal(clientRef); 109 | emailNotificationId = response.data.id; 110 | }) 111 | }); 112 | 113 | it('send email notification with document upload', () => { 114 | var postEmailNotificationResponseJson = require('./schemas/v2/POST_notification_email_response.json'), 115 | options = {personalisation: { name: 'Foo', documents: 116 | notifyClient.prepareUpload(Buffer.from("%PDF-1.5 testpdf")) 117 | }, reference: clientRef}; 118 | 119 | return notifyClient.sendEmail(emailTemplateId, email, options).then((response) => { 120 | response.status.should.equal(201); 121 | expect(response.data).to.be.jsonSchema(postEmailNotificationResponseJson); 122 | response.data.content.body.should.equal('Hello Foo\r\n\r\nFunctional test help make our world a better place'); 123 | response.data.content.subject.should.equal('Functional Tests are good'); 124 | response.data.reference.should.equal(clientRef); 125 | emailNotificationId = response.data.id; 126 | }) 127 | }); 128 | 129 | it('send email notification with CSV document upload', () => { 130 | var postEmailNotificationResponseJson = require('./schemas/v2/POST_notification_email_response.json'), 131 | options = {personalisation: { name: 'Foo', documents: 132 | notifyClient.prepareUpload(Buffer.from("a,b"), true) 133 | }, reference: clientRef}; 134 | 135 | return notifyClient.sendEmail(emailTemplateId, email, options).then((response) => { 136 | response.status.should.equal(201); 137 | expect(response.data).to.be.jsonSchema(postEmailNotificationResponseJson); 138 | response.data.content.body.should.equal('Hello Foo\r\n\r\nFunctional test help make our world a better place'); 139 | response.data.content.subject.should.equal('Functional Tests are good'); 140 | response.data.reference.should.equal(clientRef); 141 | emailNotificationId = response.data.id; 142 | }) 143 | }); 144 | 145 | it('send sms notification', () => { 146 | var postSmsNotificationResponseJson = require('./schemas/v2/POST_notification_sms_response.json'), 147 | options = {personalisation: personalisation}; 148 | 149 | return notifyClient.sendSms(smsTemplateId, phoneNumber, options).then((response) => { 150 | response.status.should.equal(201); 151 | expect(response.data).to.be.jsonSchema(postSmsNotificationResponseJson); 152 | response.data.content.body.should.equal('Hello Foo\n\nFunctional Tests make our world a better place'); 153 | smsNotificationId = response.data.id; 154 | }); 155 | }); 156 | 157 | it('send sms notification with sms_sender_id', () => { 158 | var postSmsNotificationResponseJson = require('./schemas/v2/POST_notification_sms_response.json'), 159 | options = {personalisation: personalisation, reference: clientRef, smsSenderId: smsSenderId}; 160 | 161 | should.exist(smsSenderId); 162 | return teamNotifyClient.sendSms(smsTemplateId, phoneNumber, options).then((response) => { 163 | response.status.should.equal(201); 164 | expect(response.data).to.be.jsonSchema(postSmsNotificationResponseJson); 165 | response.data.content.body.should.equal('Hello Foo\n\nFunctional Tests make our world a better place'); 166 | smsNotificationId = response.data.id; 167 | }); 168 | }); 169 | 170 | it('send letter notification', () => { 171 | var postLetterNotificationResponseJson = require('./schemas/v2/POST_notification_letter_response.json'), 172 | options = {personalisation: letterContact}; 173 | 174 | return notifyClient.sendLetter(letterTemplateId, options).then((response) => { 175 | response.status.should.equal(201); 176 | expect(response.data).to.be.jsonSchema(postLetterNotificationResponseJson); 177 | response.data.content.body.should.equal('Hello ' + letterContact.address_line_1); 178 | letterNotificationId = response.data.id; 179 | }); 180 | }); 181 | 182 | it('send a precompiled letter notification', () => { 183 | var postPrecompiledLetterNotificationResponseJson = require( 184 | './schemas/v2/POST_notification_precompiled_letter_response.json' 185 | ); 186 | fs.readFile('./spec/integration/test_files/one_page_pdf.pdf', function(err, pdf_file) { 187 | return notifyClient.sendPrecompiledLetter("my_reference", pdf_file, "first") 188 | .then((response) => { 189 | response.status.should.equal(201); 190 | expect(response.data).to.be.jsonSchema(postPrecompiledLetterNotificationResponseJson); 191 | letterNotificationId = response.data.id; 192 | }) 193 | }); 194 | }); 195 | 196 | var getNotificationJson = require('./schemas/v2/GET_notification_response.json'); 197 | var getNotificationsJson = require('./schemas/v2/GET_notifications_response.json'); 198 | 199 | it('get email notification by id', () => { 200 | should.exist(emailNotificationId) 201 | return notifyClient.getNotificationById(emailNotificationId).then((response) => { 202 | response.status.should.equal(200); 203 | expect(response.data).to.be.jsonSchema(getNotificationJson); 204 | response.data.type.should.equal('email'); 205 | response.data.body.should.equal('Hello Foo\r\n\r\nFunctional test help make our world a better place'); 206 | response.data.subject.should.equal('Functional Tests are good'); 207 | }); 208 | }); 209 | 210 | it('get sms notification by id', () => { 211 | should.exist(smsNotificationId) 212 | return notifyClient.getNotificationById(smsNotificationId).then((response) => { 213 | response.status.should.equal(200); 214 | expect(response.data).to.be.jsonSchema(getNotificationJson); 215 | response.data.type.should.equal('sms'); 216 | response.data.body.should.equal('Hello Foo\n\nFunctional Tests make our world a better place'); 217 | }); 218 | }); 219 | 220 | it('get letter notification by id', () => { 221 | should.exist(letterNotificationId) 222 | return notifyClient.getNotificationById(letterNotificationId).then((response) => { 223 | response.status.should.equal(200); 224 | expect(response.data).to.be.jsonSchema(getNotificationJson); 225 | response.data.type.should.equal('letter'); 226 | response.data.body.should.equal('Hello ' + letterContact.address_line_1); 227 | }); 228 | }); 229 | 230 | it('get a letter pdf', (done) => { 231 | should.exist(letterNotificationId) 232 | pdf_contents = fs.readFileSync('./spec/integration/test_files/one_page_pdf.pdf'); 233 | var count = 0 234 | // it takes a while for the pdf for a freshly sent letter to become ready, so we need to retry the promise 235 | // a few times, and apply delay in between the attempts. Since our function returns a promise, 236 | // and it's asynchronous, we cannot use a regular loop to do that. That's why we envelop our promise in a 237 | // function which we call up to 7 times or until the promise is resolved. 238 | const tryClient = () => { 239 | return notifyClient.getPdfForLetterNotification(letterNotificationId) 240 | .then((file_buffer) => { 241 | expect(pdf_contents).to.equalBytes(file_buffer); 242 | done(); 243 | }) 244 | .catch((err) => { 245 | if (err.constructor.name === 'AssertionError') { 246 | done(err); 247 | } 248 | if (err.response.data && (err.response.data.errors[0].error === "PDFNotReadyError")) { 249 | count += 1 250 | if (count < 24) { 251 | setTimeout(tryClient, 5000) 252 | } else { 253 | done(new Error('Too many PDFNotReadyError errors')); 254 | } 255 | }; 256 | }) 257 | }; 258 | tryClient() 259 | 260 | }); 261 | 262 | it('get all notifications', () => { 263 | chai.tv4.addSchema('notification.json', getNotificationJson); 264 | return notifyClient.getNotifications().then((response) => { 265 | response.should.have.property('status', 200); 266 | expect(response.data).to.be.jsonSchema(getNotificationsJson); 267 | }); 268 | }); 269 | 270 | }); 271 | 272 | describe('templates', () => { 273 | 274 | var getTemplateJson = require('./schemas/v2/GET_template_by_id.json'); 275 | var getTemplatesJson = require('./schemas/v2/GET_templates_response.json'); 276 | var postTemplatePreviewJson = require('./schemas/v2/POST_template_preview.json'); 277 | var getReceivedTextJson = require('./schemas/v2/GET_received_text_response.json'); 278 | var getReceivedTextsJson = require('./schemas/v2/GET_received_texts_response.json'); 279 | 280 | it('get sms template by id', () => { 281 | return notifyClient.getTemplateById(smsTemplateId).then((response) => { 282 | response.status.should.equal(200); 283 | expect(response.data).to.be.jsonSchema(getTemplateJson); 284 | response.data.name.should.equal('Client Functional test sms template'); 285 | should.not.exist(response.data.subject); 286 | should.not.exist(response.data.letter_contact_block); 287 | }); 288 | }); 289 | 290 | it('get email template by id', () => { 291 | return notifyClient.getTemplateById(emailTemplateId).then((response) => { 292 | response.status.should.equal(200); 293 | expect(response.data).to.be.jsonSchema(getTemplateJson); 294 | response.data.body.should.equal('Hello ((name))\r\n\r\nFunctional test help make our world a better place'); 295 | response.data.name.should.equal('Client Functional test email template'); 296 | response.data.subject.should.equal('Functional Tests are good'); 297 | should.not.exist(response.data.letter_contact_block); 298 | }); 299 | }); 300 | 301 | it('get letter template by id', () => { 302 | return notifyClient.getTemplateById(letterTemplateId).then((response) => { 303 | response.status.should.equal(200); 304 | expect(response.data).to.be.jsonSchema(getTemplateJson); 305 | response.data.body.should.equal('Hello ((address_line_1))'); 306 | response.data.name.should.equal('Client functional letter template'); 307 | response.data.subject.should.equal('Main heading'); 308 | response.data.letter_contact_block.should.equal( 309 | 'Government Digital Service\nThe White Chapel Building\n' + 310 | '10 Whitechapel High Street\nLondon\nE1 8QS\nUnited Kingdom' 311 | ); 312 | }); 313 | }); 314 | 315 | it('get sms template by id and version', () => { 316 | return notifyClient.getTemplateByIdAndVersion(smsTemplateId, 1).then((response) => { 317 | response.status.should.equal(200); 318 | expect(response.data).to.be.jsonSchema(getTemplateJson); 319 | response.data.name.should.equal('Example text message template'); 320 | should.not.exist(response.data.subject); 321 | response.data.version.should.equal(1); 322 | should.not.exist(response.data.letter_contact_block); 323 | }); 324 | }); 325 | 326 | it('get email template by id and version', () => { 327 | return notifyClient.getTemplateByIdAndVersion(emailTemplateId, 1).then((response) => { 328 | response.status.should.equal(200); 329 | expect(response.data).to.be.jsonSchema(getTemplateJson); 330 | response.data.name.should.equal('Client Functional test email template'); 331 | response.data.version.should.equal(1); 332 | }); 333 | }); 334 | 335 | it('get letter template by id and version', () => { 336 | return notifyClient.getTemplateByIdAndVersion(letterTemplateId, 1).then((response) => { 337 | response.status.should.equal(200); 338 | expect(response.data).to.be.jsonSchema(getTemplateJson); 339 | response.data.name.should.equal('Untitled'); 340 | response.data.version.should.equal(1); 341 | // version 1 of the template had no letter_contact_block 342 | should.not.exist(response.data.letter_contact_block); 343 | }); 344 | }); 345 | 346 | it('get all templates', () => { 347 | return notifyClient.getAllTemplates().then((response) => { 348 | response.status.should.equal(200); 349 | expect(response.data).to.be.jsonSchema(getTemplatesJson); 350 | }); 351 | }); 352 | 353 | it('get sms templates', () => { 354 | return notifyClient.getAllTemplates('sms').then((response) => { 355 | response.status.should.equal(200); 356 | expect(response.data).to.be.jsonSchema(getTemplatesJson); 357 | }); 358 | }); 359 | 360 | it('get email templates', () => { 361 | return notifyClient.getAllTemplates('email').then((response) => { 362 | response.status.should.equal(200); 363 | expect(response.data).to.be.jsonSchema(getTemplatesJson); 364 | }); 365 | }); 366 | 367 | it('get letter templates', () => { 368 | return notifyClient.getAllTemplates('letter').then((response) => { 369 | response.status.should.equal(200); 370 | expect(response.data).to.be.jsonSchema(getTemplatesJson); 371 | }); 372 | }); 373 | 374 | it('preview sms template', () => { 375 | var personalisation = { "name": "Foo" } 376 | return notifyClient.previewTemplateById(smsTemplateId, personalisation).then((response) => { 377 | response.status.should.equal(200); 378 | expect(response.data).to.be.jsonSchema(postTemplatePreviewJson); 379 | response.data.type.should.equal('sms'); 380 | should.not.exist(response.data.subject); 381 | }); 382 | }); 383 | 384 | it('preview email template', () => { 385 | var personalisation = { "name": "Foo" } 386 | return notifyClient.previewTemplateById(emailTemplateId, personalisation).then((response) => { 387 | response.status.should.equal(200); 388 | expect(response.data).to.be.jsonSchema(postTemplatePreviewJson); 389 | response.data.type.should.equal('email'); 390 | should.exist(response.data.subject); 391 | }); 392 | }); 393 | 394 | it('preview letter template', () => { 395 | var personalisation = { "address_line_1": "Foo", "address_line_2": "Bar", "postcode": "SW1 1AA" } 396 | return notifyClient.previewTemplateById(letterTemplateId, personalisation).then((response) => { 397 | response.status.should.equal(200); 398 | expect(response.data).to.be.jsonSchema(postTemplatePreviewJson); 399 | response.data.type.should.equal('letter'); 400 | should.exist(response.data.subject); 401 | }); 402 | }); 403 | 404 | it('get all received texts', () => { 405 | chai.tv4.addSchema('receivedText.json', getReceivedTextJson); 406 | return receivedTextClient.getReceivedTexts().then((response) => { 407 | response.status.should.equal(200); 408 | expect(response.data).to.be.jsonSchema(getReceivedTextsJson); 409 | expect(response.data["received_text_messages"]).to.be.an('array').that.is.not.empty; 410 | }); 411 | }); 412 | 413 | }); 414 | 415 | }); 416 | -------------------------------------------------------------------------------- /spec/integration/test_files/one_page_pdf.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/notifications-node-client/7d04f771ec69bb8513edb062d09492ea82450f67/spec/integration/test_files/one_page_pdf.pdf -------------------------------------------------------------------------------- /spec/notification.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai'); 2 | const chaiBytes = require('chai-bytes'); 3 | chai.use(chaiBytes); 4 | 5 | let expect = require('chai').expect, 6 | NotifyClient = require('../client/notification.js').NotifyClient, 7 | MockDate = require('mockdate'), 8 | nock = require('nock'), 9 | createGovukNotifyToken = require('../client/authentication.js'); 10 | 11 | MockDate.set(1234567890000); 12 | 13 | const baseUrl = 'http://localhost'; 14 | const serviceId = 'c745a8d8-b48a-4b0d-96e5-dbea0165ebd1'; 15 | const apiKeyId = '8b3aa916-ec82-434e-b0c5-d5d9b371d6a3'; 16 | 17 | function getNotifyClient() { 18 | let baseUrl = 'http://localhost'; 19 | let notifyClient = new NotifyClient(baseUrl, serviceId, apiKeyId); 20 | return notifyClient; 21 | } 22 | 23 | function getNotifyAuthNock() { 24 | let notifyNock = nock(baseUrl, { 25 | reqheaders: { 26 | 'Authorization': 'Bearer ' + createGovukNotifyToken('POST', '/v2/notifications/', apiKeyId, serviceId) 27 | } 28 | }) 29 | return notifyNock; 30 | } 31 | 32 | describe('notification api', () => { 33 | 34 | beforeEach(() => { 35 | MockDate.set(1234567890000); 36 | }); 37 | 38 | afterEach(() => { 39 | MockDate.reset(); 40 | }); 41 | 42 | let notifyClient = getNotifyClient(); 43 | let notifyAuthNock = getNotifyAuthNock(); 44 | 45 | describe('sendEmail', () => { 46 | it('should send an email', () => { 47 | 48 | let email = 'dom@example.com', 49 | templateId = '123', 50 | options = { 51 | personalisation: {foo: 'bar'}, 52 | }, 53 | data = { 54 | template_id: templateId, 55 | email_address: email, 56 | personalisation: options.personalisation 57 | }; 58 | 59 | notifyAuthNock 60 | .post('/v2/notifications/email', data) 61 | .reply(200, {hooray: 'bkbbk'}); 62 | 63 | return notifyClient.sendEmail(templateId, email, options) 64 | .then(function (response) { 65 | expect(response.status).to.equal(200); 66 | }); 67 | 68 | }); 69 | 70 | it('should send an email with email_reply_to_id', () => { 71 | 72 | let email = 'dom@example.com', 73 | templateId = '123', 74 | options = { 75 | personalisation: {foo: 'bar'}, 76 | emailReplyToId: '456', 77 | }, 78 | data = { 79 | template_id: templateId, 80 | email_address: email, 81 | personalisation: options.personalisation, 82 | email_reply_to_id: options.emailReplyToId 83 | }; 84 | 85 | notifyAuthNock 86 | .post('/v2/notifications/email', data) 87 | .reply(200, {hooray: 'bkbbk'}); 88 | 89 | return notifyClient.sendEmail(templateId, email, options) 90 | .then((response) => { 91 | expect(response.status).to.equal(200); 92 | }); 93 | }); 94 | 95 | it('should send an email with a one click unsubscribe URL', () => { 96 | 97 | let email = 'me@example.com', 98 | templateId = '123', 99 | options = { 100 | personalisation: {foo: 'bar'}, 101 | oneClickUnsubscribeURL: '456', 102 | }, 103 | data = { 104 | template_id: templateId, 105 | email_address: email, 106 | personalisation: options.personalisation, 107 | one_click_unsubscribe_url: options.oneClickUnsubscribeURL 108 | }; 109 | 110 | notifyAuthNock 111 | .post('/v2/notifications/email', data) 112 | .reply(200, {hooray: 'bkbbk'}); 113 | 114 | return notifyClient.sendEmail(templateId, email, options) 115 | .then((response) => { 116 | expect(response.status).to.equal(200); 117 | }); 118 | }); 119 | 120 | it('should send an email with document upload', () => { 121 | let email = 'dom@example.com', 122 | templateId = '123', 123 | options = { 124 | personalisation: {documents: 125 | notifyClient.prepareUpload(Buffer.from("%PDF-1.5 testpdf")) 126 | }, 127 | }, 128 | data = { 129 | template_id: templateId, 130 | email_address: email, 131 | personalisation: options.personalisation, 132 | }; 133 | 134 | notifyAuthNock 135 | .post('/v2/notifications/email', data) 136 | .reply(200, {hooray: 'bkbbk'}); 137 | 138 | return notifyClient.sendEmail(templateId, email, options) 139 | .then((response) => { 140 | expect(response.status).to.equal(200); 141 | }); 142 | }); 143 | 144 | it('should send an email with a custom filename', () => { 145 | let email = 'dom@example.com', 146 | templateId = '123', 147 | options = { 148 | personalisation: {documents: 149 | notifyClient.prepareUpload(Buffer.from("a,b"), {filename: 'report.csv'}) 150 | }, 151 | }, 152 | data = { 153 | template_id: templateId, 154 | email_address: email, 155 | personalisation: options.personalisation, 156 | }; 157 | 158 | notifyAuthNock 159 | .post('/v2/notifications/email', data) 160 | .reply(200, {hooray: 'bkbbk'}); 161 | 162 | return notifyClient.sendEmail(templateId, email, options) 163 | .then((response) => { 164 | expect(response.status).to.equal(200); 165 | expect(response.config.data).to.include('"filename":"report.csv"'); 166 | }); 167 | }); 168 | 169 | it('should reject options dicts with unknown options', () => { 170 | let email = 'foo@bar.com', 171 | templateId = '123', 172 | // old personalisation dict 173 | options = { 174 | firstname: 'Fred', 175 | surname: 'Smith', 176 | reference: 'ABC123' 177 | }; 178 | return notifyClient.sendEmail(templateId, email, options) 179 | .catch((err) => expect(err.message).to.include('["firstname","surname"]')); 180 | }); 181 | }); 182 | 183 | describe('prepareUpload', () => { 184 | it('should throw error when file bigger than 2MB is supplied', () => { 185 | let file = Buffer.alloc(3*1024*1024) 186 | expect(function(){ 187 | notifyClient.prepareUpload(file); 188 | }).to.throw("File is larger than 2MB.") 189 | }); 190 | it('should accept files as buffers (from fs.readFile with no encoding)', () => { 191 | let fs = require('fs'); 192 | let file = fs.readFileSync('./spec/test_files/simple.csv'); 193 | expect(typeof(file)).to.equal('object') 194 | expect(Buffer.isBuffer(file)).to.equal(true); 195 | expect( 196 | notifyClient.prepareUpload(file) 197 | ).contains({file: 'MSwyLDMKYSxiLGMK'}) 198 | }); 199 | it('should accept files as strings (from fs.readFile with an encoding)', () => { 200 | let fs = require('fs'); 201 | let file = fs.readFileSync('./spec/test_files/simple.csv', 'binary'); 202 | expect(typeof(file)).to.equal('string') 203 | expect(Buffer.isBuffer(file)).to.equal(false); 204 | expect( 205 | notifyClient.prepareUpload(file) 206 | ).contains({file: 'MSwyLDMKYSxiLGMK'}) 207 | }); 208 | 209 | it('should allow send a file email confirmation to be disabled', () => { 210 | let file = Buffer.alloc(2*1024*1024) 211 | expect( 212 | notifyClient.prepareUpload(file, {confirmEmailBeforeDownload: false}) 213 | ).contains({confirm_email_before_download: false, retention_period: null}) 214 | }); 215 | 216 | it('should allow send a file email confirmation to be set', () => { 217 | let file = Buffer.alloc(2*1024*1024) 218 | expect( 219 | notifyClient.prepareUpload(file, {confirmEmailBeforeDownload: true}) 220 | ).contains({confirm_email_before_download: true, retention_period: null}) 221 | }); 222 | 223 | it('should allow custom retention periods to be set', () => { 224 | let file = Buffer.alloc(2*1024*1024) 225 | expect( 226 | notifyClient.prepareUpload(file, {retentionPeriod: "52 weeks"}) 227 | ).contains({confirm_email_before_download: null, retention_period: '52 weeks'}) 228 | }); 229 | 230 | it('should allow custom filenames to be set', () => { 231 | let file = Buffer.alloc(2*1024*1024) 232 | expect( 233 | notifyClient.prepareUpload(file, {filename: "report.csv"}) 234 | ).contains({confirm_email_before_download: null, filename: "report.csv"}) 235 | }); 236 | }); 237 | 238 | describe('sendSms', () => { 239 | 240 | it('should send an sms', () => { 241 | 242 | let phoneNo = '07525755555', 243 | templateId = '123', 244 | options = { 245 | personalisation: {foo: 'bar'}, 246 | }, 247 | data = { 248 | template_id: templateId, 249 | phone_number: phoneNo, 250 | personalisation: options.personalisation 251 | }; 252 | 253 | notifyAuthNock 254 | .post('/v2/notifications/sms', data) 255 | .reply(200, {hooray: 'bkbbk'}); 256 | 257 | return notifyClient.sendSms(templateId, phoneNo, options) 258 | .then(function (response) { 259 | expect(response.status).to.equal(200); 260 | }); 261 | }); 262 | 263 | it('should send an sms with smsSenderId', () => { 264 | 265 | let phoneNo = '07525755555', 266 | templateId = '123', 267 | options = { 268 | personalisation: {foo: 'bar'}, 269 | smsSenderId: '456', 270 | }, 271 | data = { 272 | template_id: templateId, 273 | phone_number: phoneNo, 274 | personalisation: options.personalisation, 275 | sms_sender_id: options.smsSenderId, 276 | }; 277 | 278 | notifyAuthNock 279 | .post('/v2/notifications/sms', data) 280 | .reply(200, {hooray: 'bkbbk'}); 281 | 282 | return notifyClient.sendSms(templateId, phoneNo, options) 283 | .then(function (response) { 284 | expect(response.status).to.equal(200); 285 | }); 286 | }); 287 | 288 | it('should reject options dicts with unknown options', () => { 289 | let phoneNumber = '07123456789', 290 | templateId = '123', 291 | // old personalisation dict 292 | options = { 293 | firstname: 'Fred', 294 | surname: 'Smith', 295 | reference: 'ABC123' 296 | }; 297 | return notifyClient.sendSms(templateId, phoneNumber, options) 298 | .catch((err) => expect(err.message).to.include('["firstname","surname"]')); 299 | }); 300 | }); 301 | 302 | describe('sendLetter', () => { 303 | 304 | it('should send a letter', () => { 305 | 306 | let templateId = '123', 307 | options = { 308 | personalisation: { 309 | address_line_1: 'Mr Tester', 310 | address_line_2: '1 Test street', 311 | postcode: 'NW1 2UN' 312 | }, 313 | }, 314 | data = { 315 | template_id: templateId, 316 | personalisation: options.personalisation 317 | }; 318 | 319 | notifyAuthNock 320 | .post('/v2/notifications/letter', data) 321 | .reply(200, {hooray: 'bkbbk'}); 322 | 323 | return notifyClient.sendLetter(templateId, options) 324 | .then(function (response) { 325 | expect(response.status).to.equal(200); 326 | }); 327 | }); 328 | 329 | it('should reject options dicts with unknown options', () => { 330 | let templateId = '123', 331 | // old personalisation dict 332 | options = { 333 | address_line_1: 'Mr Tester', 334 | address_line_2: '1 Test street', 335 | postcode: 'NW1 2UN', 336 | reference: 'ABC123' 337 | }; 338 | return notifyClient.sendLetter(templateId, options) 339 | .catch((err) => expect(err.message).to.include('["address_line_1","address_line_2","postcode"]')); 340 | }); 341 | 342 | }); 343 | 344 | it('should get notification by id', () => { 345 | 346 | let notificationId = 'wfdfdgf'; 347 | 348 | notifyAuthNock 349 | .get('/v2/notifications/' + notificationId) 350 | .reply(200, {hooray: 'bkbbk'}); 351 | 352 | return notifyClient.getNotificationById(notificationId) 353 | .then(function (response) { 354 | expect(response.status).to.equal(200); 355 | }); 356 | }); 357 | 358 | it('should get letter pdf by id', () => { 359 | 360 | let pdf_file = Buffer.from("%PDF-1.5 testpdf") 361 | let notificationId = 'wfdfdgf'; 362 | 363 | notifyAuthNock 364 | .get('/v2/notifications/' + notificationId + '/pdf') 365 | .reply(200, pdf_file.toString()); 366 | 367 | return notifyClient.getPdfForLetterNotification(notificationId) 368 | .then(function (response_buffer) { 369 | expect(response_buffer).to.equalBytes(pdf_file) 370 | }); 371 | }); 372 | 373 | 374 | describe('sendPrecompiledLetter', () => { 375 | 376 | it('should send a precompiled letter', () => { 377 | let pdf_file = Buffer.from("%PDF-1.5 testpdf"), 378 | reference = "HORK", 379 | data = {"reference": reference, "content": pdf_file.toString('base64')} 380 | 381 | notifyAuthNock 382 | .post('/v2/notifications/letter', data) 383 | .reply(200, {hiphip: 'hooray'}); 384 | 385 | return notifyClient.sendPrecompiledLetter(reference, pdf_file) 386 | .then(function (response) { 387 | expect(response.status).to.equal(200); 388 | }); 389 | }); 390 | 391 | 392 | it('should be able to set postage when sending a precompiled letter', () => { 393 | let pdf_file = Buffer.from("%PDF-1.5 testpdf"), 394 | reference = "HORK", 395 | postage = "first" 396 | data = {"reference": reference, "content": pdf_file.toString('base64'), "postage": postage} 397 | 398 | notifyAuthNock 399 | .post('/v2/notifications/letter', data) 400 | .reply(200, {hiphip: 'hooray'}); 401 | 402 | return notifyClient.sendPrecompiledLetter(reference, pdf_file, postage) 403 | .then(function (response) { 404 | expect(response.status).to.equal(200); 405 | }); 406 | }); 407 | 408 | it('should throw error when file bigger than 5MB is supplied', () => { 409 | let file = Buffer.alloc(6*1024*1024), 410 | reference = "HORK" 411 | expect(function(){ 412 | notifyClient.sendPrecompiledLetter(reference, file); 413 | }).to.throw("File is larger than 5MB.") 414 | }); 415 | }); 416 | 417 | 418 | describe('getNotifications', () => { 419 | 420 | it('should get all notifications', () => { 421 | 422 | notifyAuthNock 423 | .get('/v2/notifications') 424 | .reply(200, {hooray: 'bkbbk'}); 425 | 426 | return notifyClient.getNotifications() 427 | .then(function (response) { 428 | expect(response.status).to.equal(200); 429 | }); 430 | }); 431 | 432 | it('should get all notifications with a reference', () => { 433 | 434 | let reference = 'myref'; 435 | 436 | notifyAuthNock 437 | .get('/v2/notifications?reference=' + reference) 438 | .reply(200, {hooray: 'bkbbk'}); 439 | 440 | return notifyClient.getNotifications(undefined, undefined, reference) 441 | .then(function (response) { 442 | expect(response.status).to.equal(200); 443 | }); 444 | }); 445 | 446 | it('should get all failed notifications', () => { 447 | 448 | let status = 'failed'; 449 | 450 | notifyAuthNock 451 | .get('/v2/notifications?status=' + status) 452 | .reply(200, {hooray: 'bkbbk'}); 453 | 454 | return notifyClient.getNotifications(undefined, 'failed') 455 | .then(function (response) { 456 | expect(response.status).to.equal(200); 457 | }); 458 | }); 459 | 460 | it('should get all failed sms notifications', () => { 461 | 462 | let templateType = 'sms'; 463 | let status = 'failed'; 464 | 465 | notifyAuthNock 466 | .get('/v2/notifications?template_type=' + templateType + '&status=' + status) 467 | .reply(200, {hooray: 'bkbbk'}); 468 | 469 | return notifyClient.getNotifications(templateType, status) 470 | .then(function (response) { 471 | expect(response.status).to.equal(200); 472 | }); 473 | }); 474 | 475 | it('should get all delivered sms notifications with a reference', () => { 476 | 477 | let templateType = 'sms'; 478 | let status = 'delivered'; 479 | let reference = 'myref'; 480 | 481 | notifyAuthNock 482 | .get('/v2/notifications?template_type=' + templateType + '&status=' + status + '&reference=' + reference) 483 | .reply(200, {hooray: 'bkbbk'}); 484 | 485 | return notifyClient.getNotifications(templateType, status, reference) 486 | .then(function (response) { 487 | expect(response.status).to.equal(200); 488 | }); 489 | }); 490 | 491 | it('should get all failed email notifications with a reference older than a given notification', () => { 492 | 493 | let templateType = 'sms'; 494 | let status = 'delivered'; 495 | let reference = 'myref'; 496 | let olderThanId = '35836a9e-5a97-4d99-8309-0c5a2c3dbc72'; 497 | 498 | notifyAuthNock 499 | .get('/v2/notifications?template_type=' + templateType + 500 | '&status=' + status + 501 | '&reference=' + reference + 502 | '&older_than=' + olderThanId 503 | ) 504 | .reply(200, {hooray: 'bkbbk'}); 505 | 506 | return notifyClient.getNotifications(templateType, status, reference, olderThanId) 507 | .then(function (response) { 508 | expect(response.status).to.equal(200); 509 | }); 510 | }); 511 | }); 512 | 513 | describe('template funtions', () => { 514 | 515 | it('should get template by id', () => { 516 | 517 | let templateId = '35836a9e-5a97-4d99-8309-0c5a2c3dbc72'; 518 | 519 | notifyAuthNock 520 | .get('/v2/template/' + templateId) 521 | .reply(200, {foo: 'bar'}); 522 | 523 | return notifyClient.getTemplateById(templateId) 524 | .then(function (response) { 525 | expect(response.status).to.equal(200); 526 | }); 527 | 528 | }); 529 | 530 | it('should get template by id and version', () => { 531 | 532 | let templateId = '35836a9e-5a97-4d99-8309-0c5a2c3dbc72'; 533 | let version = 10; 534 | 535 | notifyAuthNock 536 | .get('/v2/template/' + templateId + '/version/' + version) 537 | .reply(200, {foo: 'bar'}); 538 | 539 | return notifyClient.getTemplateByIdAndVersion(templateId, version) 540 | .then(function (response) { 541 | expect(response.status).to.equal(200); 542 | }); 543 | 544 | }); 545 | 546 | it('should get all templates with unspecified template type', () => { 547 | 548 | notifyAuthNock 549 | .get('/v2/templates') 550 | .reply(200, {foo: 'bar'}); 551 | 552 | return notifyClient.getAllTemplates() 553 | .then(function (response) { 554 | expect(response.status).to.equal(200); 555 | }); 556 | 557 | }); 558 | 559 | it('should get all templates with unspecified template type', () => { 560 | 561 | let templateType = 'sms' 562 | 563 | notifyAuthNock 564 | .get('/v2/templates?type=' + templateType) 565 | .reply(200, {foo: 'bar'}); 566 | 567 | return notifyClient.getAllTemplates(templateType) 568 | .then(function (response) { 569 | expect(response.status).to.equal(200); 570 | }); 571 | 572 | }); 573 | 574 | it('should preview template by id with personalisation', () => { 575 | 576 | let templateId = '35836a9e-5a97-4d99-8309-0c5a2c3dbc72'; 577 | let payload = {name: 'Foo' } 578 | let expectedPersonalisation = {personalisation: payload }; 579 | 580 | notifyAuthNock 581 | .post('/v2/template/' + templateId + '/preview', expectedPersonalisation) 582 | .reply(200, {foo: 'bar'}); 583 | 584 | return notifyClient.previewTemplateById(templateId, payload) 585 | .then(function (response) { 586 | expect(response.status).to.equal(200); 587 | }); 588 | 589 | }); 590 | 591 | it('should preview template by id without personalisation', () => { 592 | 593 | let templateId = '35836a9e-5a97-4d99-8309-0c5a2c3dbc72'; 594 | 595 | notifyAuthNock 596 | .post('/v2/template/' + templateId + '/preview') 597 | .reply(200, {foo: 'bar'}); 598 | 599 | return notifyClient.previewTemplateById(templateId) 600 | .then(function (response) { 601 | expect(response .status).to.equal(200); 602 | }); 603 | }); 604 | }); 605 | 606 | it('should get latest 250 received texts', function() { 607 | 608 | notifyAuthNock 609 | .get('/v2/received-text-messages') 610 | .reply(200, {"foo":"bar"}); 611 | 612 | return notifyClient.getReceivedTexts() 613 | .then(function(response){ 614 | expect(response.status).to.equal(200); 615 | }); 616 | }); 617 | 618 | it('should get up to next 250 received texts with a reference older than a given message id', function() { 619 | 620 | var olderThanId = '35836a9e-5a97-4d99-8309-0c5a2c3dbc72'; 621 | 622 | notifyAuthNock 623 | .get('/v2/received-text-messages?older_than=' + olderThanId) 624 | .reply(200, {"foo":"bar"}); 625 | 626 | return notifyClient.getReceivedTexts(olderThanId) 627 | .then(function(response){ 628 | expect(response.status).to.equal(200); 629 | }); 630 | }); 631 | }); 632 | 633 | MockDate.reset(); 634 | -------------------------------------------------------------------------------- /spec/test_files/simple.csv: -------------------------------------------------------------------------------- 1 | 1,2,3 2 | a,b,c 3 | --------------------------------------------------------------------------------