├── .github └── workflows │ └── node-tests.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── Makefile ├── Readme.md ├── examples ├── international_address_autocomplete.js ├── international_street.js ├── us_autocomplete_pro.js ├── us_enrichment.js ├── us_extract.js ├── us_reverse_geo.js ├── us_street.js └── us_zipcode.js ├── index.mjs ├── js-sdk-demo.cast ├── package-lock.json ├── package.json ├── rollup.config.mjs ├── src ├── AgentSender.js ├── BaseUrlSender.js ├── Batch.js ├── ClientBuilder.js ├── CustomHeaderSender.js ├── Errors.js ├── HttpSender.js ├── InputData.js ├── LicenseSender.js ├── Request.js ├── Response.js ├── RetrySender.js ├── SharedCredentials.js ├── SigningSender.js ├── StaticCredentials.js ├── StatusCodeSender.js ├── international_address_autocomplete │ ├── Client.js │ ├── Lookup.js │ └── Suggestion.js ├── international_street │ ├── Candidate.js │ ├── Client.js │ └── Lookup.js ├── us_autocomplete_pro │ ├── Client.js │ ├── Lookup.js │ └── Suggestion.js ├── us_enrichment │ ├── Client.js │ ├── Lookup.js │ └── Response.js ├── us_extract │ ├── Address.js │ ├── Client.js │ ├── Lookup.js │ └── Result.js ├── us_reverse_geo │ ├── Client.js │ ├── Lookup.js │ ├── Response.js │ └── Result.js ├── us_street │ ├── Candidate.js │ ├── Client.js │ └── Lookup.js ├── us_zipcode │ ├── Client.js │ ├── Lookup.js │ └── Result.js └── util │ ├── Sleeper.js │ ├── apiToSDKKeyMap.js │ ├── buildClients.js │ ├── buildInputData.js │ ├── buildSmartyResponse.js │ └── sendBatch.js └── tests ├── fixtures ├── MockSleeper.js └── mock_senders.js ├── international_address_autocomplete ├── test_Client.js └── test_Lookup.js ├── international_street ├── test_Candidate.js ├── test_Client.js └── test_Lookup.js ├── test_AgentSender.js ├── test_BaseUrlSender.js ├── test_Batch.js ├── test_CustomHeaderSender.js ├── test_ExtractExample.mjs ├── test_HttpSender.js ├── test_LicenseSender.js ├── test_RetrySender.js ├── test_SigningSender.js ├── test_StatusCodeSender.js ├── us_autocomplete_pro ├── test_Client.js ├── test_Lookup.js └── test_Suggestion.js ├── us_enrichment ├── test_Client.js ├── test_Lookup.js └── test_Response.js ├── us_extract ├── test_Address.js ├── test_Client.js ├── test_Lookup.js └── test_Result.js ├── us_reverse_geo ├── test_Client.js ├── test_Lookup.js └── test_Response.js ├── us_street ├── test_Candidate.js ├── test_Client.js └── test_Lookup.js └── us_zipcode ├── test_Client.js └── test_Result.js /.github/workflows/node-tests.yml: -------------------------------------------------------------------------------- 1 | name: Node Tests 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | strategy: 15 | matrix: 16 | node-version: [16.x, 18.x, 20.x] 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v1 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | - run: npm ci 25 | - run: npm test 26 | env: 27 | CI: true 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *DS_Store* 2 | *.swp 3 | __MACOSX 4 | /.idea/ 5 | /node_modules/ 6 | /dist/ 7 | .env -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | See the changelog repository: 4 | 5 | github.com/smartystreets/changelog/blob/master/sdk/javascript.md -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2022 Smarty 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | VERSION := $(shell tagit -p --dry-run) 4 | VERSION_FILE1 := package.json 5 | VERSION_FILE2 := package-lock.json 6 | 7 | test: node_modules 8 | npm run test 9 | 10 | node_modules: 11 | npm install 12 | 13 | build: 14 | npm run build 15 | 16 | publish: test version build upload unversion 17 | tagit -p 18 | git push origin --tags 19 | 20 | upload: 21 | npm publish 22 | 23 | version: 24 | sed -i.bak -e 's/^ "version": "0\.0\.0",/ "version": "$(VERSION)",/g' "$(VERSION_FILE1)" && rm -f "$(VERSION_FILE1).bak" 25 | sed -i.bak -e 's/^ "version": "0\.0\.0",/ "version": "$(VERSION)",/g' "$(VERSION_FILE2)" && rm -f "$(VERSION_FILE2).bak" 26 | 27 | unversion: 28 | git checkout "$(VERSION_FILE1)" "$(VERSION_FILE2)" 29 | 30 | # node_modules is a real directory target 31 | .PHONY: test publish upload version unversion 32 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | #### SMARTY DISCLAIMER: Subject to the terms of the associated license agreement, this software is freely available for your use. This software is FREE, AS IN PUPPIES, and is a gift. Enjoy your new responsibility. This means that while we may consider enhancement requests, we may or may not choose to entertain requests at our sole and absolute discretion. 2 | 3 | # Smarty JavaScript SDK 4 | 5 | The official client libraries for accessing Smarty APIs with JavaScript. 6 | 7 | [![asciicast](https://asciinema.org/a/189101.png)](https://asciinema.org/a/189101) 8 | 9 | You may have noticed this page is curiously sparse. Don't panic, there's [documentation](https://www.smarty.com/docs/sdk/javascript) as well as working [examples](examples) over on our website. 10 | 11 | [Apache 2.0 License](LICENSE) 12 | -------------------------------------------------------------------------------- /examples/international_address_autocomplete.js: -------------------------------------------------------------------------------- 1 | const SmartySDK = require("smartystreets-javascript-sdk"); 2 | const SmartyCore = SmartySDK.core; 3 | const Lookup = SmartySDK.internationalAddressAutocomplete.Lookup; 4 | 5 | // for Server-to-server requests, use this code: 6 | // let authId = process.env.SMARTY_AUTH_ID; 7 | // let authToken = process.env.SMARTY_AUTH_TOKEN; 8 | // const credentials = new SmartyCore.StaticCredentials(authId, authToken); 9 | 10 | // for client-side requests (browser/mobile), use this code: 11 | const key = process.env.SMARTY_EMBEDDED_KEY; 12 | const credentials = new SmartyCore.SharedCredentials(key); 13 | 14 | // The appropriate license values to be used for your subscriptions 15 | // can be found on the Subscription page of the account dashboard. 16 | // https://www.smarty.com/docs/cloud/licensing 17 | const clientBuilder = new SmartyCore.ClientBuilder(credentials).withLicenses(["international-autocomplete-v2-cloud"]) 18 | // .withBaseUrl("YOUR URL") // withBaseUrl() should be used if you are self-hosting the Smarty API 19 | 20 | const client = clientBuilder.buildInternationalAddressAutocompleteClient(); 21 | 22 | // Documentation for input fields can be found at: 23 | // www.smarty.com/docs/cloud/international-address-autocomplete-api#pro-http-request-input-fields 24 | const country = "CAN"; 25 | 26 | const summaryLookup = new Lookup({search: "123 Anson", country}); 27 | // uncomment the following line to add a custom parameter 28 | // summaryLookup.addCustomParameter("max_results", 1); 29 | 30 | await handleRequest(summaryLookup, "Response of summary results"); 31 | 32 | const detailedLookup = new Lookup({addressId: summaryLookup.result[0].addressId, country}); 33 | await handleRequest(detailedLookup, "Response using an address ID to get detailed results"); 34 | 35 | function logSuggestions(response, message) { 36 | console.log("*** " + message + " ***"); 37 | 38 | response.result.forEach(suggestion => { 39 | if (suggestion.addressText) { 40 | console.log("Entries: ", suggestion.entries); 41 | console.log("Address Text: ", suggestion.addressText); 42 | console.log("Address ID: ", suggestion.addressId); 43 | } else { 44 | console.log("Street: ", suggestion.street); 45 | console.log("Locality: ", suggestion.locality); 46 | console.log("Administrative Area: ", suggestion.administrativeArea); 47 | console.log("Postal Code: ", suggestion.postalCode); 48 | console.log("Country: ", suggestion.countryIso3); 49 | } 50 | }); 51 | console.log("\n"); 52 | } 53 | 54 | async function handleRequest(lookup, lookupType) { 55 | try { 56 | const results = await client.send(lookup); 57 | logSuggestions(results, lookupType); 58 | } catch(err) { 59 | console.log(err) 60 | } 61 | } -------------------------------------------------------------------------------- /examples/international_street.js: -------------------------------------------------------------------------------- 1 | const SmartySDK = require("smartystreets-javascript-sdk"); 2 | const SmartyCore = SmartySDK.core; 3 | const Lookup = SmartySDK.internationalStreet.Lookup; 4 | 5 | // for Server-to-server requests, use this code: 6 | // let authId = process.env.SMARTY_AUTH_ID; 7 | // let authToken = process.env.SMARTY_AUTH_TOKEN; 8 | // const credentials = new SmartyCore.StaticCredentials(authId, authToken); 9 | 10 | // for client-side requests (browser/mobile), use this code: 11 | let key = process.env.SMARTY_EMBEDDED_KEY; 12 | const credentials = new SmartyCore.SharedCredentials(key); 13 | 14 | // The appropriate license values to be used for your subscriptions 15 | // can be found on the Subscription page of the account dashboard. 16 | // https://www.smarty.com/docs/cloud/licensing 17 | let clientBuilder = new SmartyCore.ClientBuilder(credentials).withLicenses(["international-global-plus-cloud"]); 18 | // .withBaseUrl("YOUR URL") // withBaseUrl() should be used if you are self-hosting the Smarty API 19 | 20 | let client = clientBuilder.buildInternationalStreetClient(); 21 | 22 | // Documentation for input fields can be found at: 23 | // https://www.smarty.com/docs/cloud/international-street-api#http-input-fields 24 | 25 | let lookup1 = new Lookup("CA", "262 Browndale Cr, Richmond Hill, ON"); 26 | // uncomment the following line to add a custom parameter 27 | // lookup1.addCustomParameter("input_id", 1234); 28 | 29 | let lookup2 = new Lookup(); 30 | lookup2.inputId = "ID-8675309"; 31 | lookup2.geocode = false; 32 | lookup2.organization = "John Doe"; 33 | lookup2.address1 = "Rua Padre Antonio D'Angelo 121"; 34 | lookup2.address2 = "Casa Verde"; 35 | lookup2.locality = "Sao Paulo"; 36 | lookup2.administrativeArea = "SP"; 37 | lookup2.country = "Brazil"; 38 | lookup2.postalCode = "02516-050"; 39 | 40 | await handleRequest(lookup1) 41 | await handleRequest(lookup2) 42 | 43 | function displayResult(result) { 44 | console.log(result.result[0].components); 45 | } 46 | 47 | function handleError(error) { 48 | console.log("ERROR:", error); 49 | } 50 | 51 | async function handleRequest(lookup) { 52 | try { 53 | const result = await client.send(lookup); 54 | displayResult(result); 55 | } catch(err) { 56 | handleError(err); 57 | } 58 | } -------------------------------------------------------------------------------- /examples/us_autocomplete_pro.js: -------------------------------------------------------------------------------- 1 | const SmartySDK = require("smartystreets-javascript-sdk"); 2 | const SmartyCore = SmartySDK.core; 3 | const Lookup = SmartySDK.usAutocompletePro.Lookup; 4 | 5 | // for Server-to-server requests, use this code: 6 | // let authId = process.env.SMARTY_AUTH_ID; 7 | // let authToken = process.env.SMARTY_AUTH_TOKEN; 8 | // const credentials = new SmartyCore.StaticCredentials(authId, authToken); 9 | 10 | // for client-side requests (browser/mobile), use this code: 11 | let key = process.env.SMARTY_EMBEDDED_KEY; 12 | const credentials = new SmartyCore.SharedCredentials(key); 13 | 14 | // The appropriate license values to be used for your subscriptions 15 | // can be found on the Subscription page of the account dashboard. 16 | // https://www.smarty.com/docs/cloud/licensing 17 | let clientBuilder = new SmartyCore.ClientBuilder(credentials).withLicenses(["us-autocomplete-pro-cloud"]); 18 | // .withBaseUrl("YOUR URL") // withBaseUrl() should be used if you are self-hosting the Smarty API 19 | 20 | let client = clientBuilder.buildUsAutocompleteProClient(); 21 | 22 | // Documentation for input fields can be found at: 23 | // https://www.smarty.com/docs/cloud/us-autocomplete-api#pro-http-request-input-fields 24 | 25 | // *** Simple Lookup *** 26 | let lookup = new Lookup("4770 Lincoln"); 27 | // uncomment the following line to add a custom parameter 28 | // lookup.addCustomParameter("max_results", 3); 29 | 30 | await handleRequest(lookup, "Simple Lookup"); 31 | 32 | // *** Using Filter and Prefer *** 33 | lookup = new Lookup("4770 Lincoln"); 34 | 35 | lookup.maxResults = 10; 36 | lookup.includeOnlyCities = ["Chicago,La Grange,IL", "Blaine,WA"]; 37 | lookup.preferStates = ["IL"]; 38 | lookup.preferRatio = 33; 39 | lookup.source = "all"; 40 | 41 | await handleRequest(lookup, "Using Filter and Prefer"); 42 | 43 | // *** Using 'selected' to Expand Secondaries *** 44 | lookup = new Lookup("4770 Lincoln"); 45 | 46 | lookup.selected = "4770 N Lincoln Ave Ste 2 (3) Chicago, IL 60625"; 47 | 48 | await handleRequest(lookup, "Using 'selected' to Expand Secondaries") 49 | 50 | // ************************************************ 51 | 52 | function logSuggestions(response, message) { 53 | console.log(message); 54 | console.log(response.result); 55 | console.log("*********************"); 56 | } 57 | 58 | async function handleRequest(lookup, lookupType) { 59 | try { 60 | const results = await client.send(lookup); 61 | logSuggestions(results, lookupType); 62 | } catch(err) { 63 | console.log(err) 64 | } 65 | } -------------------------------------------------------------------------------- /examples/us_enrichment.js: -------------------------------------------------------------------------------- 1 | const SmartySDK = require("smartystreets-javascript-sdk"); 2 | const SmartyCore = SmartySDK.core; 3 | const Lookup = SmartySDK.usEnrichment.Lookup; 4 | 5 | // for Server-to-server requests, use this code: 6 | // let authId = process.env.SMARTY_AUTH_ID; 7 | // let authToken = process.env.SMARTY_AUTH_TOKEN; 8 | // const credentials = new SmartyCore.StaticCredentials(authId, authToken); 9 | 10 | // for client-side requests (browser/mobile), use this code: 11 | let key = process.env.SMARTY_EMBEDDED_KEY; 12 | const credentials = new SmartyCore.SharedCredentials(key); 13 | 14 | // The appropriate license values to be used for your subscriptions 15 | // can be found on the Subscription page of the account dashboard. 16 | // https://www.smarty.com/docs/cloud/licensing 17 | let clientBuilder = new SmartyCore.ClientBuilder(credentials).withLicenses(["us-property-data-principal-cloud"]); 18 | // .withBaseUrl("YOUR URL") // withBaseUrl() should be used if you are self-hosting the Smarty API 19 | 20 | let client = clientBuilder.buildUsEnrichmentClient(); 21 | 22 | // Documentation for input fields can be found at: 23 | // https://www.smarty.com/docs/us-street-api#input-fields 24 | 25 | let lookup = new Lookup("334968275"); 26 | // uncomment the following line to add a custom parameter 27 | // lookup.addCustomParameter("include", "group_financial"); 28 | 29 | handleResponse(lookup).then(); 30 | 31 | async function handleResponse(lookup) { 32 | try { 33 | const result = await client.sendPrincipal(lookup); 34 | console.log(result.response); 35 | } catch (err) { 36 | console.log(err); 37 | } 38 | } -------------------------------------------------------------------------------- /examples/us_extract.js: -------------------------------------------------------------------------------- 1 | const SmartySDK = require("smartystreets-javascript-sdk"); 2 | const SmartyCore = SmartySDK.core; 3 | const Lookup = SmartySDK.usExtract.Lookup; 4 | 5 | // for Server-to-server requests, use this code: 6 | // let authId = process.env.SMARTY_AUTH_ID; 7 | // let authToken = process.env.SMARTY_AUTH_TOKEN; 8 | // const credentials = new SmartyCore.StaticCredentials(authId, authToken); 9 | 10 | // for client-side requests (browser/mobile), use this code: 11 | let key = process.env.SMARTY_EMBEDDED_KEY; 12 | const credentials = new SmartyCore.SharedCredentials(key); 13 | 14 | let clientBuilder = new SmartyCore.ClientBuilder(credentials); 15 | // .withBaseUrl("YOUR URL") // withBaseUrl() should be used if you are self-hosting the Smarty API 16 | 17 | let client = clientBuilder.buildUsExtractClient(); 18 | 19 | // Documentation for input fields can be found at: 20 | // https://www.smarty.com/docs/cloud/us-extract-api#http-request-input-fields 21 | 22 | let lookup = new Lookup("If you work at 1600 Pennsylvania Ave NW, Washington DC you're gonna have a hard time."); 23 | lookup.aggressive = true; 24 | lookup.addressesHaveLineBreaks = false; 25 | lookup.addressesPerLine = 1; 26 | 27 | // uncomment the following line to add a custom parameter 28 | // lookup.addCustomParameter("addr_line_breaks", false); 29 | 30 | await handleRequest(lookup); 31 | 32 | function logResult(response) { 33 | console.log(response.result); 34 | } 35 | 36 | async function handleRequest(lookup) { 37 | try { 38 | const response = await client.send(lookup); 39 | logResult(response); 40 | } catch(err) { 41 | console.log(err); 42 | } 43 | } -------------------------------------------------------------------------------- /examples/us_reverse_geo.js: -------------------------------------------------------------------------------- 1 | const SmartySDK = require("smartystreets-javascript-sdk"); 2 | const SmartyCore = SmartySDK.core; 3 | const Lookup = SmartySDK.usReverseGeo.Lookup; 4 | 5 | // for Server-to-server requests, use this code: 6 | // let authId = process.env.SMARTY_AUTH_ID; 7 | // let authToken = process.env.SMARTY_AUTH_TOKEN; 8 | // const credentials = new SmartyCore.StaticCredentials(authId, authToken); 9 | 10 | // for client-side requests (browser/mobile), use this code: 11 | let key = process.env.SMARTY_EMBEDDED_KEY; 12 | const credentials = new SmartyCore.SharedCredentials(key); 13 | 14 | // The appropriate license values to be used for your subscriptions 15 | // can be found on the Subscription page of the account dashboard. 16 | // https://www.smarty.com/docs/cloud/licensing 17 | let clientBuilder = new SmartyCore.ClientBuilder(credentials).withLicenses(["us-reverse-geocoding-cloud"]); 18 | // .withBaseUrl("YOUR URL") // withBaseUrl() should be used if you are self-hosting the Smarty API 19 | let client = clientBuilder.buildUsReverseGeoClient(); 20 | 21 | let lookup1 = new Lookup(40.27644, -111.65747); 22 | // uncomment the following line to add a custom parameter 23 | // lookup1.addCustomParameter("source", "all"); 24 | 25 | await handleResponse(lookup1); 26 | 27 | function displayResult(result) { 28 | console.log(result.response.results[0].address); 29 | } 30 | 31 | function handleError(error) { 32 | console.log("ERROR:", error); 33 | } 34 | 35 | async function handleResponse(lookup) { 36 | try { 37 | const result = await client.send(lookup); 38 | displayResult(result); 39 | } catch(err) { 40 | handleError(err); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /examples/us_street.js: -------------------------------------------------------------------------------- 1 | const SmartySDK = require("smartystreets-javascript-sdk"); 2 | const SmartyCore = SmartySDK.core; 3 | const Lookup = SmartySDK.usStreet.Lookup; 4 | 5 | // for Server-to-server requests, use this code: 6 | // let authId = process.env.SMARTY_AUTH_ID; 7 | // let authToken = process.env.SMARTY_AUTH_TOKEN; 8 | // const credentials = new SmartyCore.StaticCredentials(authId, authToken); 9 | 10 | // for client-side requests (browser/mobile), use this code: 11 | let key = process.env.SMARTY_EMBEDDED_KEY; 12 | const credentials = new SmartyCore.SharedCredentials(key); 13 | 14 | // The appropriate license values to be used for your subscriptions 15 | // can be found on the Subscription page of the account dashboard. 16 | // https://www.smarty.com/docs/cloud/licensing 17 | let clientBuilder = new SmartyCore.ClientBuilder(credentials).withLicenses(["us-rooftop-geocoding-cloud"]); 18 | // .withBaseUrl("YOUR URL") // withBaseUrl() should be used if you are self-hosting the Smarty API 19 | 20 | let client = clientBuilder.buildUsStreetApiClient(); 21 | 22 | // Documentation for input fields can be found at: 23 | // https://www.smarty.com/docs/us-street-api#input-fields 24 | 25 | let lookup1 = new Lookup(); 26 | lookup1.inputId = "24601"; // Optional ID from your system 27 | lookup1.addressee = "John Doe"; 28 | lookup1.street = "330 N 100 W"; 29 | lookup1.street2 = "closet under the stairs"; 30 | lookup1.secondary = "APT 2"; 31 | lookup1.urbanization = ""; // Only applies to Puerto Rico addresses 32 | lookup1.city = "Provo"; 33 | lookup1.state = "Utah"; 34 | lookup1.zipCode = "84601"; 35 | lookup1.maxCandidates = 3; 36 | lookup1.match = "invalid"; // "invalid" is the most permissive match, 37 | // this will always return at least one result even if the address is invalid. 38 | // Refer to the documentation for additional MatchStrategy options. 39 | 40 | let lookup2 = new Lookup(); 41 | lookup2.street = "1600 Amphitheater Pkwy"; 42 | lookup2.lastLine = "Mountainview, CA"; 43 | lookup2.maxCandidates = 5; 44 | 45 | let lookup3 = new Lookup(); 46 | lookup3.inputId = "8675309"; 47 | lookup3.street = "1600 Amphitheatre Parkway Mountain View, CA 94043"; 48 | 49 | // uncomment the following line to add a custom parameter 50 | // lookup3.addCustomParameter("max_candidates", 1); 51 | 52 | // NOTE: batches are not supported when using SharedCredentials. 53 | let batch = new SmartyCore.Batch(); 54 | batch.add(lookup1); 55 | batch.add(lookup2); 56 | batch.add(lookup3); 57 | 58 | await handleResponse(batch); 59 | 60 | function handleSuccess(response) { 61 | response.lookups.map(lookup => console.log(lookup.result)); 62 | } 63 | 64 | function handleError(response) { 65 | console.log(response); 66 | } 67 | 68 | async function handleResponse(lookup) { 69 | try { 70 | const result = await client.send(lookup); 71 | handleSuccess(result); 72 | } catch(err) { 73 | handleError(err); 74 | } 75 | } -------------------------------------------------------------------------------- /examples/us_zipcode.js: -------------------------------------------------------------------------------- 1 | const SmartySDK = require("smartystreets-javascript-sdk"); 2 | const SmartyCore = SmartySDK.core; 3 | const Lookup = SmartySDK.usZipcode.Lookup; 4 | 5 | // for Server-to-server requests, use this code: 6 | // let authId = process.env.SMARTY_AUTH_ID; 7 | // let authToken = process.env.SMARTY_AUTH_TOKEN; 8 | // const credentials = new SmartyCore.StaticCredentials(authId, authToken); 9 | 10 | // for client-side requests (browser/mobile), use this code: 11 | let key = process.env.SMARTY_EMBEDDED_KEY; 12 | const credentials = new SmartyCore.SharedCredentials(key); 13 | 14 | let clientBuilder = new SmartyCore.ClientBuilder(credentials); 15 | // .withBaseUrl("YOUR URL") // withBaseUrl() should be used if you are self-hosting the Smarty API 16 | 17 | let client = clientBuilder.buildUsZipcodeClient(); 18 | 19 | // Documentation for input fields can be found at: 20 | // https://www.smarty.com/docs/us-zipcode-api#input-fields 21 | 22 | let lookup1 = new Lookup(); 23 | lookup1.inputId = "01189998819991197253"; // Optional ID from your system 24 | lookup1.zipCode = "49786"; 25 | 26 | let lookup2 = new Lookup(); 27 | lookup2.inputId = "dfc33cb6-829e-4fea-aa1b-b6d6580f0817"; 28 | lookup2.city = "Provo"; 29 | lookup2.state = "UT"; 30 | lookup2.zipCode = "84604"; 31 | 32 | let lookup3 = new Lookup(); 33 | lookup3.city = "Phoenix"; 34 | lookup3.state = "AZ"; 35 | 36 | // uncomment the following line to add a custom parameter 37 | // lookup3.addCustomParameter("input_id", 1234); 38 | 39 | let batch = new SmartyCore.Batch(); 40 | batch.add(lookup1); 41 | batch.add(lookup2); 42 | batch.add(lookup3); 43 | 44 | await handleResponse(batch); 45 | 46 | function viewResults(response) { 47 | response.lookups.map(lookup => lookup.result.map(candidate => { 48 | candidate.cities.map(city => console.log(city.city)); 49 | // candidate.zipcodes.map(zipcode => console.log(zipcode.zipcode)); 50 | })); 51 | } 52 | 53 | async function handleResponse(lookup) { 54 | try { 55 | const result = await client.send(lookup); 56 | viewResults(result); 57 | } catch(err) { 58 | console.log(err); 59 | } 60 | } -------------------------------------------------------------------------------- /index.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * this files is the entry point for rollup to bundle the library 3 | * it exports all the classes and functions as named exports 4 | */ 5 | import Batch from "./src/Batch.js"; 6 | import ClientBuilder from "./src/ClientBuilder.js"; 7 | import buildClient from "./src/util/buildClients.js"; 8 | import SharedCredentials from "./src/SharedCredentials.js"; 9 | import StaticCredentials from "./src/StaticCredentials.js"; 10 | import Errors from "./src/Errors.js"; 11 | 12 | import LookupUSStreet from "./src/us_street/Lookup.js"; 13 | import CandidateUSStreet from "./src/us_street/Candidate.js"; 14 | 15 | import LookupUSZipcode from "./src/us_zipcode/Lookup.js"; 16 | import ResultUSZipcode from "./src/us_zipcode/Result.js"; 17 | 18 | import LookupUSAutocompletePro from "./src/us_autocomplete_pro/Lookup.js"; 19 | import SuggestionUSAutocompletePro from "./src/us_autocomplete_pro/Suggestion.js"; 20 | 21 | import LookupUSExtract from "./src/us_extract/Lookup.js"; 22 | import ResultUSExtract from "./src/us_extract/Result.js"; 23 | 24 | import LookupInternationalStreet from "./src/international_street/Lookup.js"; 25 | import CandidateInternationalStreet from "./src/international_street/Candidate.js"; 26 | 27 | import LookupUSReverseGeo from "./src/us_reverse_geo/Lookup.js"; 28 | 29 | import LookupInternationalAddressAutocomplete from "./src/international_address_autocomplete/Lookup.js"; 30 | import SuggestionInternationalAddressAutocomplete from "./src/international_address_autocomplete/Suggestion.js"; 31 | 32 | import LookupUSEnrichment from "./src/us_enrichment/Lookup.js"; 33 | import ResponseUSEnrichment from "./src/us_enrichment/Response.js"; 34 | 35 | export const core = { 36 | Batch, 37 | ClientBuilder, 38 | buildClient, 39 | SharedCredentials, 40 | StaticCredentials, 41 | Errors, 42 | }; 43 | 44 | export const usStreet = { 45 | Lookup: LookupUSStreet, 46 | Candidate: CandidateUSStreet, 47 | }; 48 | 49 | export const usZipcode = { 50 | Lookup: LookupUSZipcode, 51 | Result: ResultUSZipcode, 52 | }; 53 | 54 | export const usAutocompletePro = { 55 | Lookup: LookupUSAutocompletePro, 56 | Suggestion: SuggestionUSAutocompletePro, 57 | }; 58 | 59 | export const usExtract = { 60 | Lookup: LookupUSExtract, 61 | Result: ResultUSExtract, 62 | }; 63 | 64 | export const internationalStreet = { 65 | Lookup: LookupInternationalStreet, 66 | Candidate: CandidateInternationalStreet, 67 | }; 68 | 69 | export const usReverseGeo = { 70 | Lookup: LookupUSReverseGeo, 71 | }; 72 | 73 | export const internationalAddressAutocomplete = { 74 | Lookup: LookupInternationalAddressAutocomplete, 75 | Suggestion: SuggestionInternationalAddressAutocomplete, 76 | }; 77 | 78 | export const usEnrichment = { 79 | Lookup: LookupUSEnrichment, 80 | Response: ResponseUSEnrichment, 81 | }; 82 | 83 | export default { 84 | core, 85 | usStreet, 86 | usZipcode, 87 | usAutocompletePro, 88 | usExtract, 89 | internationalStreet, 90 | usReverseGeo, 91 | internationalAddressAutocomplete, 92 | usEnrichment, 93 | }; 94 | -------------------------------------------------------------------------------- /js-sdk-demo.cast: -------------------------------------------------------------------------------- 1 | {"version": 2, "width": 117, "height": 45, "timestamp": 1530134182, "idle_time_limit": 1.0, "env": {"SHELL": "/bin/bash", "TERM": "xterm-256color"}} 2 | [0.010229, "o", "\u001b[?1034h$ "] 3 | [0.467947, "o", "n"] 4 | [0.524226, "o", "p"] 5 | [0.70808, "o", "m"] 6 | [1.052436, "o", " "] 7 | [1.348151, "o", "i"] 8 | [1.452182, "o", "n"] 9 | [1.523968, "o", "s"] 10 | [1.667973, "o", "t"] 11 | [1.764448, "o", "a"] 12 | [1.907995, "o", "l"] 13 | [2.052337, "o", "l"] 14 | [2.196398, "o", " "] 15 | [2.68405, "o", "s"] 16 | [2.804179, "o", "m"] 17 | [2.915962, "o", "a"] 18 | [3.10013, "o", "r"] 19 | [3.315956, "o", "t"] 20 | [3.796418, "o", "y"] 21 | [4.068496, "o", "s"] 22 | [4.244361, "o", "t"] 23 | [4.332117, "o", "r"] 24 | [4.620157, "o", "e"] 25 | [4.748372, "o", "e"] 26 | [4.93247, "o", "t"] 27 | [5.756319, "o", "s"] 28 | [6.340584, "o", "-"] 29 | [6.860414, "o", "j"] 30 | [6.971776, "o", "a"] 31 | [7.268031, "o", "v"] 32 | [7.427947, "o", "a"] 33 | [7.900124, "o", "s"] 34 | [8.06054, "o", "c"] 35 | [8.340057, "o", "r"] 36 | [8.604094, "o", "i"] 37 | [8.708032, "o", "p"] 38 | [9.020098, "o", "t"] 39 | [9.443566, "o", "-"] 40 | [10.420419, "o", "s"] 41 | [10.578756, "o", "d"] 42 | [11.075609, "o", "k"] 43 | [11.619051, "o", "\r\n"] 44 | [11.994868, "o", "\u001b[?25l"] 45 | [11.997323, "o", "⸨\u001b[90m░░░░░░░░░░░░░░░░░░\u001b[0m⸩ ⠇ rollbackFailedOptional: \u001b[34;40mverb\u001b[0m \u001b[35mnpm-session\u001b[0m 80cbcd75622a93e0\u001b[0m\u001b[K\r"] 46 | [12.392273, "o", "⸨\u001b[90m░░░░░░░░░░░░░░░░░░\u001b[0m⸩ ⠇ rollbackFailedOptional: \u001b[34;40mverb\u001b[0m \u001b[35mnpm-session\u001b[0m 80cbcd75622a93e0\u001b[0m\u001b[K\r"] 47 | [12.443351, "o", "⸨\u001b[90m░░░░░░░░░░░░░░░░░░\u001b[0m⸩ ⠇ rollbackFailedOptional: \u001b[34;40mverb\u001b[0m \u001b[35mnpm-session\u001b[0m 80cbcd75622a93e0\u001b[0m\u001b[K\r"] 48 | [12.493195, "o", "⸨\u001b[90m░░░░░░░░░░░░░░░░░░\u001b[0m⸩ ⠇ rollbackFailedOptional: \u001b[34;40mverb\u001b[0m \u001b[35mnpm-session\u001b[0m 80cbcd75622a93e0\u001b[0m\u001b[K\r"] 49 | [12.547335, "o", "⸨\u001b[90m░░░░░░░░░░░░░░░░░░\u001b[0m⸩ ⠴ loadDep:promise: \u001b[7msill\u001b[0m \u001b[35mresolveWithNewModule\u001b[0m smartystreets-javascript-sdk@1.0.\u001b[0m\u001b[K\r"] 50 | [12.601971, "o", "⸨\u001b[7m \u001b[27m\u001b[90m░░░░░░░░░░░\u001b[0m⸩ ⠴ extract:smartystreets-javascript-sdk: \u001b[34;40mverb\u001b[0m \u001b[35mlock\u001b[0m using /Users/Neo/.npm/_locks\u001b[0m\u001b[K\r"] 51 | [12.656729, "o", "⸨\u001b[7m \u001b[27m\u001b[90m░░░░░░░░░░░\u001b[0m⸩ ⠴ extract:smartystreets-javascript-sdk: \u001b[34;40mverb\u001b[0m \u001b[35mlock\u001b[0m using /Users/Neo/.npm/_locks\u001b[0m\u001b[K\r"] 52 | [12.712334, "o", "⸨\u001b[7m \u001b[27m\u001b[90m░░░░░░░░░░░\u001b[0m⸩ ⠴ extract:smartystreets-javascript-sdk: \u001b[34;40mverb\u001b[0m \u001b[35mlock\u001b[0m using /Users/Neo/.npm/_locks\u001b[0m\u001b[K\r"] 53 | [12.765091, "o", "⸨\u001b[7m \u001b[27m\u001b[90m░░░░░░░░░░░\u001b[0m⸩ ⠴ extract:smartystreets-javascript-sdk: \u001b[34;40mverb\u001b[0m \u001b[35mlock\u001b[0m using /Users/Neo/.npm/_locks\u001b[0m\u001b[K\r"] 54 | [12.815536, "o", "⸨\u001b[7m \u001b[27m\u001b[90m░░░░░░░░░░░\u001b[0m⸩ ⠴ extract:smartystreets-javascript-sdk: \u001b[34;40mverb\u001b[0m \u001b[35mlock\u001b[0m using /Users/Neo/.npm/_locks\u001b[0m\u001b[K\r"] 55 | [12.837371, "o", "\r\u001b[K\u001b[?25h"] 56 | [12.854763, "o", "\r\n"] 57 | [12.855413, "o", "+ smartystreets-javascript-sdk@1.0.4\r\nadded 1 package in 0.872s\r\n"] 58 | [12.863697, "o", "$ "] 59 | [14.195072, "o", "\r\n"] 60 | [14.195235, "o", "$ "] 61 | [14.354464, "o", "\r\n"] 62 | [14.354626, "o", "$ "] 63 | [14.546544, "o", "\r\n"] 64 | [14.546779, "o", "$ "] 65 | [15.211593, "o", "c"] 66 | [15.355119, "o", "a"] 67 | [15.692111, "o", "t"] 68 | [15.892141, "o", " "] 69 | [16.956311, "o", "e"] 70 | [17.788102, "o", "x"] 71 | [18.03553, "o", "ample.js "] 72 | [18.522916, "o", "\r\n"] 73 | [18.527357, "o", "const SmartyStreetsSDK = require(\"smartystreets-javascript-sdk\");\r\nconst SmartyStreetsCore = SmartyStreetsSDK.core;\r\nconst Lookup = SmartyStreetsSDK.usStreet.Lookup;\r\n\r\n// This keypair will have been deleted by the time you are watching this video...\r\nlet authId = \"41c45025-e7a5-1ae5-bb76-8647a26f45ba\";\r\nlet authToken = \"xmztSBdqN6TVO5nUvAa8\";\r\n\r\nconsole.log(\"Step 0. Wire up the client with your keypair.\");\r\nlet clientBuilder = new SmartyStreetsCore.ClientBuilder(new SmartyStreetsCore.StaticCredentials(authId, authToken));\r\nlet client = clientBuilder.buildUsStreetApiClient();\r\n\r\nconsole.log(\"Step 1. Make a lookup. (BTW, you can also send entire batches of lookups...)\");\r\nlet lookup = new Lookup();\r\nlookup.street = \"1 Rosedale\";\r\nlookup.city = \"Baltimore\";\r\nlookup.state = \"MD\";\r\nlookup.maxCandidates = 10;\r\n\r\nconsole.log(\"Step 2. Send the lookup.\");\r\nclient.send(lookup)\r\n\t.then(handleSuccess)\r\n\t.catch(handleError);\r\n\r\nfunction handleSuccess(response) {\r\n\tconsole.log(\"Step 3. Show the resulting candidate address"] 74 | [18.527549, "o", "es:\");\r\n\tlet lookup = response.lookups[0];\r\n\tlookup.result.map(candidate => console.log(` ${candidate.deliveryLine1}, ${candidate.lastLine}`));\r\n}\r\n\r\nfunction handleError(response) {\r\n\tconsole.log(response);\r\n}\r\n"] 75 | [18.527961, "o", "$ "] 76 | [20.515216, "o", "\r\n"] 77 | [20.515496, "o", "$ "] 78 | [20.698906, "o", "\r\n"] 79 | [20.698986, "o", "$ "] 80 | [23.308251, "o", "n"] 81 | [23.396131, "o", "o"] 82 | [23.492332, "o", "d"] 83 | [23.587821, "o", "e"] 84 | [23.940086, "o", " "] 85 | [25.244151, "o", "e"] 86 | [25.476053, "o", "x"] 87 | [25.627144, "o", "ample.js "] 88 | [26.819458, "o", "\r\n"] 89 | [26.925891, "o", "Step 0. Wire up the client with your keypair.\r\n"] 90 | [26.926939, "o", "Step 1. Make a lookup. (BTW, you can also send entire batches of lookups...)\r\n"] 91 | [26.927133, "o", "Step 2. Send the lookup.\r\n"] 92 | [27.183185, "o", "Step 3. Show the resulting candidate addresses:\r\n"] 93 | [27.183252, "o", " 1 N Rosedale St, Baltimore MD 21229-3737\r\n 1 S Rosedale St, Baltimore MD 21229-3739\r\n"] 94 | [27.18749, "o", "$ "] 95 | [32.083944, "o", "exit\r\n"] 96 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "smartystreets-javascript-sdk", 3 | "version": "0.0.0", 4 | "description": "Quick and easy Smarty address validation.", 5 | "keywords": [ 6 | "smarty", 7 | "smartystreets", 8 | "address", 9 | "validation", 10 | "verification", 11 | "verify", 12 | "validate", 13 | "street-address", 14 | "geocoding", 15 | "addresses", 16 | "zipcode", 17 | "autocomplete", 18 | "autosuggest", 19 | "suggestions", 20 | "international", 21 | "http", 22 | "sdk" 23 | ], 24 | "files": [ 25 | "dist", 26 | "README.md", 27 | "LICENSE" 28 | ], 29 | "main": "dist/cjs/index.cjs", 30 | "module": "dist/esm/index.mjs", 31 | "exports": { 32 | ".": { 33 | "import": "./dist/esm/index.mjs", 34 | "default": "./dist/cjs/index.cjs" 35 | } 36 | }, 37 | "scripts": { 38 | "build": "rollup --config", 39 | "test": "mocha 'tests/**/*.{mjs,js}'" 40 | }, 41 | "author": "Smarty SDK Team (https://www.smarty.com)", 42 | "license": "Apache-2.0", 43 | "repository": { 44 | "type": "git", 45 | "url": "github:smartystreets/smartystreets-javascript-sdk" 46 | }, 47 | "devDependencies": { 48 | "@babel/preset-env": "^7.25.8", 49 | "@rollup/plugin-commonjs": "^28.0.1", 50 | "@rollup/plugin-json": "^6.1.0", 51 | "@rollup/plugin-node-resolve": "^15.3.0", 52 | "@rollup/plugin-terser": "^0.4.4", 53 | "chai": "^4.3.6", 54 | "mocha": "^10.2.0", 55 | "rollup": "^4.22.5", 56 | "rollup-plugin-delete": "^2.1.0" 57 | }, 58 | "dependencies": { 59 | "axios": "^1.7.7", 60 | "axios-retry": "^4.5.0" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import del from "rollup-plugin-delete"; 2 | import commonjs from "@rollup/plugin-commonjs"; 3 | import json from "@rollup/plugin-json"; 4 | import terser from "@rollup/plugin-terser"; 5 | import { nodeResolve } from '@rollup/plugin-node-resolve'; 6 | 7 | export default { 8 | input: "index.mjs", 9 | external: ["axios", "axios-retry"], 10 | output: [ 11 | { 12 | dir: "dist/cjs", 13 | format: "cjs", 14 | preserveModules: true, 15 | preserveModulesRoot: "src", 16 | exports: "named", 17 | entryFileNames: "[name].cjs", 18 | }, 19 | { 20 | dir: "dist/esm", 21 | format: "esm", 22 | preserveModules: true, 23 | preserveModulesRoot: "src", 24 | exports: "named", 25 | entryFileNames: "[name].mjs", 26 | }, 27 | ], 28 | plugins: [ 29 | del({ targets: "dist/*" }), 30 | nodeResolve(), 31 | commonjs({ 32 | esmExternals: true, 33 | }), 34 | json(), 35 | terser(), 36 | ], 37 | }; -------------------------------------------------------------------------------- /src/AgentSender.js: -------------------------------------------------------------------------------- 1 | class AgentSender { 2 | constructor(innerSender) { 3 | this.sender = innerSender; 4 | } 5 | 6 | send(request) { 7 | request.parameters.agent = "smarty (sdk:javascript@" + require("../package.json").version + ")"; 8 | return new Promise((resolve, reject) => { 9 | this.sender.send(request) 10 | .then(resolve) 11 | .catch(reject); 12 | }); 13 | } 14 | } 15 | 16 | module.exports = AgentSender; -------------------------------------------------------------------------------- /src/BaseUrlSender.js: -------------------------------------------------------------------------------- 1 | class BaseUrlSender { 2 | constructor(innerSender, urlOverride) { 3 | this.urlOverride = urlOverride; 4 | this.sender = innerSender; 5 | } 6 | 7 | send(request) { 8 | return new Promise((resolve, reject) => { 9 | request.baseUrl = `${this.urlOverride}${request.baseUrlParam ? `/${request.baseUrlParam}` : ""}`; 10 | 11 | this.sender.send(request) 12 | .then(resolve) 13 | .catch(reject); 14 | }); 15 | } 16 | } 17 | 18 | module.exports = BaseUrlSender; -------------------------------------------------------------------------------- /src/Batch.js: -------------------------------------------------------------------------------- 1 | const BatchFullError = require("./Errors").BatchFullError; 2 | 3 | /** 4 | * This class contains a collection of up to 100 lookups to be sent to one of the Smarty APIs
5 | * all at once. This is more efficient than sending them one at a time. 6 | */ 7 | class Batch { 8 | constructor () { 9 | this.lookups = []; 10 | } 11 | 12 | add (lookup) { 13 | if (this.lookupsHasRoomForLookup()) this.lookups.push(lookup); 14 | else throw new BatchFullError(); 15 | } 16 | 17 | lookupsHasRoomForLookup() { 18 | const maxNumberOfLookups = 100; 19 | return this.lookups.length < maxNumberOfLookups; 20 | } 21 | 22 | length() { 23 | return this.lookups.length; 24 | } 25 | 26 | getByIndex(index) { 27 | return this.lookups[index]; 28 | } 29 | 30 | getByInputId(inputId) { 31 | return this.lookups.filter(lookup => { 32 | return lookup.inputId === inputId; 33 | })[0]; 34 | } 35 | 36 | /** 37 | * Clears the lookups stored in the batch so it can be used again.
38 | * This helps avoid the overhead of building a new Batch object for each group of lookups. 39 | */ 40 | clear () { 41 | this.lookups = []; 42 | } 43 | 44 | isEmpty () { 45 | return this.length() === 0; 46 | } 47 | } 48 | 49 | module.exports = Batch; -------------------------------------------------------------------------------- /src/ClientBuilder.js: -------------------------------------------------------------------------------- 1 | const HttpSender = require("./HttpSender"); 2 | const SigningSender = require("./SigningSender"); 3 | const BaseUrlSender = require("./BaseUrlSender"); 4 | const AgentSender = require("./AgentSender"); 5 | const StaticCredentials = require("./StaticCredentials"); 6 | const SharedCredentials = require("./SharedCredentials"); 7 | const CustomHeaderSender = require("./CustomHeaderSender"); 8 | const StatusCodeSender = require("./StatusCodeSender"); 9 | const LicenseSender = require("./LicenseSender"); 10 | const BadCredentialsError = require("./Errors").BadCredentialsError; 11 | const RetrySender = require("./RetrySender.js"); 12 | const Sleeper = require("./util/Sleeper.js"); 13 | 14 | //TODO: refactor this to work more cleanly with a bundler. 15 | const UsStreetClient = require("./us_street/Client"); 16 | const UsZipcodeClient = require("./us_zipcode/Client"); 17 | const UsAutocompleteProClient = require("./us_autocomplete_pro/Client"); 18 | const UsExtractClient = require("./us_extract/Client"); 19 | const InternationalStreetClient = require("./international_street/Client"); 20 | const UsReverseGeoClient = require("./us_reverse_geo/Client"); 21 | const InternationalAddressAutocompleteClient = require("./international_address_autocomplete/Client"); 22 | const UsEnrichmentClient = require("./us_enrichment/Client"); 23 | 24 | const INTERNATIONAL_STREET_API_URI = "https://international-street.api.smarty.com/verify"; 25 | const US_AUTOCOMPLETE_PRO_API_URL = "https://us-autocomplete-pro.api.smarty.com/lookup"; 26 | const US_EXTRACT_API_URL = "https://us-extract.api.smarty.com/"; 27 | const US_STREET_API_URL = "https://us-street.api.smarty.com/street-address"; 28 | const US_ZIP_CODE_API_URL = "https://us-zipcode.api.smarty.com/lookup"; 29 | const US_REVERSE_GEO_API_URL = "https://us-reverse-geo.api.smarty.com/lookup"; 30 | const INTERNATIONAL_ADDRESS_AUTOCOMPLETE_API_URL = "https://international-autocomplete.api.smarty.com/v2/lookup"; 31 | const US_ENRICHMENT_API_URL = "https://us-enrichment.api.smarty.com/lookup"; 32 | 33 | /** 34 | * The ClientBuilder class helps you build a client object for one of the supported Smarty APIs.
35 | * You can use ClientBuilder's methods to customize settings like maximum retries or timeout duration. These methods
36 | * are chainable, so you can usually get set up with one line of code. 37 | */ 38 | class ClientBuilder { 39 | constructor(signer) { 40 | if (noCredentialsProvided()) throw new BadCredentialsError(); 41 | 42 | this.signer = signer; 43 | this.httpSender = undefined; 44 | this.maxRetries = 5; 45 | this.maxTimeout = 10000; 46 | this.baseUrl = undefined; 47 | this.proxy = undefined; 48 | this.customHeaders = {}; 49 | this.debug = undefined; 50 | this.licenses = []; 51 | 52 | function noCredentialsProvided() { 53 | return !signer instanceof StaticCredentials || !signer instanceof SharedCredentials; 54 | } 55 | } 56 | 57 | /** 58 | * @param retries The maximum number of times to retry sending the request to the API. (Default is 5) 59 | * @return Returns this to accommodate method chaining. 60 | */ 61 | withMaxRetries(retries) { 62 | this.maxRetries = retries; 63 | return this; 64 | } 65 | 66 | /** 67 | * @param timeout The maximum time (in milliseconds) to wait for a connection, and also to wait for
68 | * the response to be read. (Default is 10000) 69 | * @return Returns this to accommodate method chaining. 70 | */ 71 | withMaxTimeout(timeout) { 72 | this.maxTimeout = timeout; 73 | return this; 74 | } 75 | 76 | /** 77 | * @param sender Default is a series of nested senders. See buildSender(). 78 | * @return Returns this to accommodate method chaining. 79 | */ 80 | withSender(sender) { 81 | this.httpSender = sender; 82 | return this; 83 | } 84 | 85 | /** 86 | * This may be useful when using a local installation of the Smarty APIs. 87 | * @param url Defaults to the URL for the API corresponding to the Client object being built. 88 | * @return Returns this to accommodate method chaining. 89 | */ 90 | withBaseUrl(url) { 91 | this.baseUrl = url; 92 | return this; 93 | } 94 | 95 | /** 96 | * Use this to specify a proxy through which to send all lookups. 97 | * @param host The host of the proxy server (do not include the port). 98 | * @param port The port on the proxy server to which you wish to connect. 99 | * @param protocol The protocol on the proxy server to which you wish to connect. If the proxy server uses HTTPS, then you must set the protocol to 'https'. 100 | * @param username The username to login to the proxy. 101 | * @param password The password to login to the proxy. 102 | * @return Returns this to accommodate method chaining. 103 | */ 104 | withProxy(host, port, protocol, username, password) { 105 | this.proxy = { 106 | host: host, 107 | port: port, 108 | protocol: protocol, 109 | }; 110 | 111 | if (username && password) { 112 | this.proxy.auth = { 113 | username: username, 114 | password: password, 115 | }; 116 | } 117 | 118 | return this; 119 | } 120 | 121 | /** 122 | * Use this to add any additional headers you need. 123 | * @param customHeaders A String to Object Map of header name/value pairs. 124 | * @return Returns this to accommodate method chaining. 125 | */ 126 | withCustomHeaders(customHeaders) { 127 | this.customHeaders = customHeaders; 128 | 129 | return this; 130 | } 131 | 132 | /** 133 | * Enables debug mode, which will print information about the HTTP request and response to console.log 134 | * @return Returns this to accommodate method chaining. 135 | */ 136 | withDebug() { 137 | this.debug = true; 138 | 139 | return this; 140 | } 141 | 142 | /** 143 | * Allows the caller to specify the subscription license (aka "track") they wish to use. 144 | * @param licenses A String Array of licenses. 145 | * @returns Returns this to accommodate method chaining. 146 | */ 147 | withLicenses(licenses) { 148 | this.licenses = licenses; 149 | 150 | return this; 151 | } 152 | 153 | buildSender() { 154 | if (this.httpSender) return this.httpSender; 155 | 156 | const httpSender = new HttpSender(this.maxTimeout, this.proxy, this.debug); 157 | const statusCodeSender = new StatusCodeSender(httpSender); 158 | const signingSender = new SigningSender(statusCodeSender, this.signer); 159 | let agentSender = new AgentSender(signingSender); 160 | if (this.maxRetries > 0) { 161 | const retrySender = new RetrySender(this.maxRetries, signingSender, new Sleeper()); 162 | agentSender = new AgentSender(retrySender); 163 | } 164 | const customHeaderSender = new CustomHeaderSender(agentSender, this.customHeaders); 165 | const baseUrlSender = new BaseUrlSender(customHeaderSender, this.baseUrl); 166 | const licenseSender = new LicenseSender(baseUrlSender, this.licenses); 167 | 168 | return licenseSender; 169 | } 170 | 171 | buildClient(baseUrl, Client) { 172 | if (!this.baseUrl) { 173 | this.baseUrl = baseUrl; 174 | } 175 | 176 | return new Client(this.buildSender()); 177 | } 178 | 179 | buildUsStreetApiClient() { 180 | return this.buildClient(US_STREET_API_URL, UsStreetClient); 181 | } 182 | 183 | buildUsZipcodeClient() { 184 | return this.buildClient(US_ZIP_CODE_API_URL, UsZipcodeClient); 185 | } 186 | 187 | buildUsAutocompleteProClient() { 188 | return this.buildClient(US_AUTOCOMPLETE_PRO_API_URL, UsAutocompleteProClient); 189 | } 190 | 191 | buildUsExtractClient() { 192 | return this.buildClient(US_EXTRACT_API_URL, UsExtractClient); 193 | } 194 | 195 | buildInternationalStreetClient() { 196 | return this.buildClient(INTERNATIONAL_STREET_API_URI, InternationalStreetClient); 197 | } 198 | 199 | buildUsReverseGeoClient() { 200 | return this.buildClient(US_REVERSE_GEO_API_URL, UsReverseGeoClient); 201 | } 202 | 203 | buildInternationalAddressAutocompleteClient() { 204 | return this.buildClient(INTERNATIONAL_ADDRESS_AUTOCOMPLETE_API_URL, InternationalAddressAutocompleteClient); 205 | } 206 | 207 | buildUsEnrichmentClient() { 208 | return this.buildClient(US_ENRICHMENT_API_URL, UsEnrichmentClient); 209 | } 210 | } 211 | 212 | module.exports = ClientBuilder; -------------------------------------------------------------------------------- /src/CustomHeaderSender.js: -------------------------------------------------------------------------------- 1 | class CustomHeaderSender { 2 | constructor(innerSender, customHeaders) { 3 | this.sender = innerSender; 4 | this.customHeaders = customHeaders; 5 | } 6 | 7 | send(request) { 8 | for (let key in this.customHeaders) { 9 | request.headers[key] = this.customHeaders[key]; 10 | } 11 | 12 | return new Promise((resolve, reject) => { 13 | this.sender.send(request) 14 | .then(resolve) 15 | .catch(reject); 16 | }); 17 | } 18 | } 19 | 20 | module.exports = CustomHeaderSender; -------------------------------------------------------------------------------- /src/Errors.js: -------------------------------------------------------------------------------- 1 | class SmartyError extends Error { 2 | constructor(message = "unexpected error") { 3 | super(message); 4 | } 5 | } 6 | 7 | class DefaultError extends SmartyError { 8 | constructor(message) { 9 | super(message); 10 | } 11 | 12 | } 13 | 14 | class BatchFullError extends SmartyError { 15 | constructor() { 16 | super("A batch can contain a max of 100 lookups."); 17 | } 18 | } 19 | 20 | class BatchEmptyError extends SmartyError { 21 | constructor() { 22 | super("A batch must contain at least 1 lookup."); 23 | } 24 | } 25 | 26 | class UndefinedLookupError extends SmartyError { 27 | constructor() { 28 | super("The lookup provided is missing or undefined. Make sure you're passing a Lookup object."); 29 | } 30 | } 31 | 32 | class BadCredentialsError extends SmartyError { 33 | constructor() { 34 | super("Unauthorized: The credentials were provided incorrectly or did not match any existing active credentials."); 35 | } 36 | } 37 | 38 | class PaymentRequiredError extends SmartyError { 39 | constructor() { 40 | super("Payment Required: There is no active subscription for the account associated with the credentials submitted with the request."); 41 | } 42 | } 43 | 44 | class RequestEntityTooLargeError extends SmartyError { 45 | constructor() { 46 | super("Request Entity Too Large: The request body has exceeded the maximum size."); 47 | } 48 | } 49 | 50 | class BadRequestError extends SmartyError { 51 | constructor() { 52 | super("Bad Request (Malformed Payload): A GET request lacked a street field or the request body of a POST request contained malformed JSON."); 53 | } 54 | } 55 | 56 | class UnprocessableEntityError extends SmartyError { 57 | constructor(message) { 58 | super(message); 59 | } 60 | } 61 | 62 | class TooManyRequestsError extends SmartyError { 63 | constructor() { 64 | super("When using the public 'embedded key' authentication, we restrict the number of requests coming from a given source over too short of a time."); 65 | } 66 | } 67 | 68 | class InternalServerError extends SmartyError { 69 | constructor() { 70 | super("Internal Server Error."); 71 | } 72 | } 73 | 74 | class ServiceUnavailableError extends SmartyError { 75 | constructor() { 76 | super("Service Unavailable. Try again later."); 77 | } 78 | } 79 | 80 | class GatewayTimeoutError extends SmartyError { 81 | constructor() { 82 | super("The upstream data provider did not respond in a timely fashion and the request failed. A serious, yet rare occurrence indeed."); 83 | } 84 | } 85 | 86 | module.exports = { 87 | BatchFullError: BatchFullError, 88 | BatchEmptyError: BatchEmptyError, 89 | UndefinedLookupError: UndefinedLookupError, 90 | BadCredentialsError: BadCredentialsError, 91 | PaymentRequiredError: PaymentRequiredError, 92 | RequestEntityTooLargeError: RequestEntityTooLargeError, 93 | BadRequestError: BadRequestError, 94 | UnprocessableEntityError: UnprocessableEntityError, 95 | TooManyRequestsError: TooManyRequestsError, 96 | InternalServerError: InternalServerError, 97 | ServiceUnavailableError: ServiceUnavailableError, 98 | GatewayTimeoutError: GatewayTimeoutError, 99 | DefaultError: DefaultError 100 | }; -------------------------------------------------------------------------------- /src/HttpSender.js: -------------------------------------------------------------------------------- 1 | const Axios = require("axios").default; 2 | const {buildSmartyResponse} = require("../src/util/buildSmartyResponse"); 3 | 4 | class HttpSender { 5 | constructor(timeout = 10000, proxyConfig, debug = false) { 6 | this.axiosInstance = Axios.create(); 7 | this.timeout = timeout; 8 | this.proxyConfig = proxyConfig; 9 | if (debug) this.enableDebug(); 10 | } 11 | 12 | buildRequestConfig({payload, parameters, headers, baseUrl}) { 13 | let config = { 14 | method: "GET", 15 | timeout: this.timeout, 16 | params: parameters, 17 | headers: headers, 18 | baseURL: baseUrl, 19 | validateStatus: function (status) { 20 | return status < 500; 21 | }, 22 | }; 23 | 24 | if (payload) { 25 | config.method = "POST"; 26 | config.data = payload; 27 | } 28 | 29 | if (this.proxyConfig) config.proxy = this.proxyConfig; 30 | return config; 31 | } 32 | 33 | send(request) { 34 | return new Promise((resolve, reject) => { 35 | let requestConfig = this.buildRequestConfig(request); 36 | 37 | this.axiosInstance(requestConfig) 38 | .then(response => { 39 | let smartyResponse = buildSmartyResponse(response); 40 | 41 | if (smartyResponse.statusCode >= 400) reject(smartyResponse); 42 | 43 | resolve(smartyResponse); 44 | }) 45 | .catch(error => reject(buildSmartyResponse(undefined, error))); 46 | }); 47 | } 48 | 49 | enableDebug() { 50 | this.axiosInstance.interceptors.request.use(request => { 51 | console.log('Request:\r\n', request); 52 | console.log('\r\n*******************************************\r\n'); 53 | return request 54 | }); 55 | 56 | this.axiosInstance.interceptors.response.use(response => { 57 | console.log('Response:\r\n'); 58 | console.log('Status:', response.status, response.statusText); 59 | console.log('Headers:', response.headers); 60 | console.log('Data:', response.data); 61 | return response 62 | }) 63 | } 64 | } 65 | 66 | module.exports = HttpSender; -------------------------------------------------------------------------------- /src/InputData.js: -------------------------------------------------------------------------------- 1 | class InputData { 2 | constructor(lookup) { 3 | this.lookup = lookup; 4 | this.data = {}; 5 | } 6 | 7 | add(apiField, lookupField) { 8 | if (this.lookupFieldIsPopulated(lookupField)) this.data[apiField] = this.formatData(this.lookup[lookupField]); 9 | } 10 | 11 | addCustomParameter(key, value) { 12 | this.data[key] = value; 13 | } 14 | 15 | formatData(field) { 16 | if (Array.isArray(field)) return field.join(";"); 17 | else return field; 18 | } 19 | 20 | lookupFieldIsPopulated(lookupField) { 21 | return this.lookup[lookupField] !== "" && this.lookup[lookupField] !== undefined; 22 | } 23 | } 24 | 25 | module.exports = InputData; -------------------------------------------------------------------------------- /src/LicenseSender.js: -------------------------------------------------------------------------------- 1 | class LicenseSender { 2 | constructor(innerSender, licenses) { 3 | this.sender = innerSender; 4 | this.licenses = licenses; 5 | } 6 | 7 | send(request) { 8 | if (this.licenses.length !== 0) { 9 | request.parameters["license"] = this.licenses.join(","); 10 | } 11 | 12 | return new Promise((resolve, reject) => { 13 | this.sender.send(request) 14 | .then(resolve) 15 | .catch(reject); 16 | }); 17 | } 18 | } 19 | 20 | module.exports = LicenseSender; -------------------------------------------------------------------------------- /src/Request.js: -------------------------------------------------------------------------------- 1 | class Request { 2 | constructor(payload, headers = {"Content-Type": "application/json; charset=utf-8"}) { 3 | this.baseUrl = ""; 4 | this.baseUrlParam = ""; 5 | this.payload = payload; 6 | this.headers = headers; 7 | 8 | this.parameters = {}; 9 | } 10 | } 11 | 12 | module.exports = Request; -------------------------------------------------------------------------------- /src/Response.js: -------------------------------------------------------------------------------- 1 | class Response { 2 | constructor (statusCode, payload, error, headers) { 3 | this.statusCode = statusCode; 4 | this.payload = payload; 5 | this.error = error; 6 | this.headers = headers; 7 | } 8 | } 9 | 10 | module.exports = Response; -------------------------------------------------------------------------------- /src/RetrySender.js: -------------------------------------------------------------------------------- 1 | class RetrySender { 2 | constructor(maxRetires = 5, inner, sleeper) { 3 | this.maxRetries = maxRetires; 4 | this.statusToRetry = [408, 429, 500, 502, 503, 504]; 5 | this.statusTooManyRequests = 429; 6 | this.maxBackoffDuration = 10; 7 | this.inner = inner; 8 | this.sleeper = sleeper; 9 | } 10 | 11 | async send(request) { 12 | let response = await this.inner.send(request); 13 | 14 | for (let i = 0; i < this.maxRetries; i++) { 15 | 16 | if (!this.statusToRetry.includes(parseInt(response.statusCode))) { 17 | break; 18 | } 19 | 20 | if (parseInt(response.statusCode) === this.statusTooManyRequests) { 21 | let secondsToBackoff = 10; 22 | if (response.headers) { 23 | const retryAfterHeader = response.headers["Retry-After"]; 24 | if (Number.isInteger(retryAfterHeader)) { 25 | secondsToBackoff = retryAfterHeader; 26 | } 27 | } 28 | await this.rateLimitBackOff(secondsToBackoff); 29 | } else { 30 | await this.backoff(i); 31 | } 32 | response = await this.inner.send(request); 33 | } 34 | 35 | return response; 36 | }; 37 | 38 | async backoff(attempt) { 39 | const backoffDuration = Math.min(attempt, this.maxBackoffDuration); 40 | console.log(`There was an error processing the request. Retrying in ${backoffDuration} seconds...`); 41 | await this.sleeper.sleep(backoffDuration); 42 | }; 43 | 44 | async rateLimitBackOff(backoffDuration) { 45 | console.log(`Rate limit reached. Retrying in ${backoffDuration} seconds...`); 46 | await this.sleeper.sleep(backoffDuration); 47 | }; 48 | } 49 | 50 | module.exports = RetrySender; -------------------------------------------------------------------------------- /src/SharedCredentials.js: -------------------------------------------------------------------------------- 1 | class SharedCredentials { 2 | constructor(authId, hostName) { 3 | this.authId = authId; 4 | this.hostName = hostName; 5 | } 6 | 7 | sign(request) { 8 | request.parameters["key"] = this.authId; 9 | if (this.hostName) request.headers["Referer"] = "https://" + this.hostName; 10 | } 11 | } 12 | 13 | module.exports = SharedCredentials; -------------------------------------------------------------------------------- /src/SigningSender.js: -------------------------------------------------------------------------------- 1 | const UnprocessableEntityError = require("./Errors").UnprocessableEntityError; 2 | const SharedCredentials = require("./SharedCredentials"); 3 | 4 | class SigningSender { 5 | constructor(innerSender, signer) { 6 | this.signer = signer; 7 | this.sender = innerSender; 8 | } 9 | 10 | send(request) { 11 | const sendingPostWithSharedCredentials = request.payload && this.signer instanceof SharedCredentials; 12 | if (sendingPostWithSharedCredentials) { 13 | const message = "Shared credentials cannot be used in batches with a length greater than 1 or when using the US Extract API."; 14 | throw new UnprocessableEntityError(message); 15 | } 16 | 17 | return new Promise((resolve, reject) => { 18 | this.signer.sign(request); 19 | this.sender.send(request) 20 | .then(resolve) 21 | .catch(reject); 22 | }); 23 | } 24 | } 25 | 26 | module.exports = SigningSender; -------------------------------------------------------------------------------- /src/StaticCredentials.js: -------------------------------------------------------------------------------- 1 | class StaticCredentials { 2 | constructor (authId, authToken) { 3 | this.authId = authId; 4 | this.authToken = authToken; 5 | } 6 | 7 | sign (request) { 8 | request.parameters["auth-id"] = this.authId; 9 | request.parameters["auth-token"] = this.authToken; 10 | } 11 | } 12 | 13 | module.exports = StaticCredentials; -------------------------------------------------------------------------------- /src/StatusCodeSender.js: -------------------------------------------------------------------------------- 1 | const Errors = require("./Errors"); 2 | 3 | class StatusCodeSender { 4 | constructor(innerSender) { 5 | this.sender = innerSender; 6 | } 7 | 8 | send(request) { 9 | return new Promise((resolve, reject) => { 10 | this.sender.send(request) 11 | .then(resolve) 12 | .catch(error => { 13 | switch (error.statusCode) { 14 | case 500: 15 | error.error = new Errors.InternalServerError(); 16 | break; 17 | 18 | case 503: 19 | error.error = new Errors.ServiceUnavailableError(); 20 | break; 21 | 22 | case 504: 23 | error.error = new Errors.GatewayTimeoutError(); 24 | break; 25 | 26 | default: 27 | error.error = new Errors.DefaultError(error && error.payload && error.payload.errors[0] && error.payload.errors[0].message); 28 | } 29 | reject(error); 30 | }); 31 | }); 32 | } 33 | } 34 | 35 | module.exports = StatusCodeSender; -------------------------------------------------------------------------------- /src/international_address_autocomplete/Client.js: -------------------------------------------------------------------------------- 1 | const Errors = require("../Errors"); 2 | const Request = require("../Request"); 3 | const Suggestion = require("./Suggestion"); 4 | const buildInputData = require("../util/buildInputData"); 5 | const keyTranslationFormat = require("../util/apiToSDKKeyMap").internationalAddressAutocomplete; 6 | 7 | class Client { 8 | constructor(sender) { 9 | this.sender = sender; 10 | } 11 | 12 | send(lookup) { 13 | if (typeof lookup === "undefined") throw new Errors.UndefinedLookupError(); 14 | 15 | let request = new Request(); 16 | request.parameters = buildInputData(lookup, keyTranslationFormat); 17 | 18 | if (lookup.addressId) { 19 | request.baseUrlParam = lookup.addressId; 20 | } 21 | 22 | return new Promise((resolve, reject) => { 23 | this.sender.send(request) 24 | .then(response => { 25 | if (response.error) reject(response.error); 26 | 27 | lookup.result = buildSuggestionsFromResponse(response.payload); 28 | resolve(lookup); 29 | }) 30 | .catch(reject); 31 | }); 32 | 33 | function buildSuggestionsFromResponse(payload) { 34 | if (payload && payload.candidates === null) return []; 35 | 36 | return payload.candidates.map(suggestion => new Suggestion(suggestion)); 37 | } 38 | } 39 | } 40 | 41 | module.exports = Client; -------------------------------------------------------------------------------- /src/international_address_autocomplete/Lookup.js: -------------------------------------------------------------------------------- 1 | class Lookup { 2 | constructor({search, addressId, country, maxResults = 5, includeOnlyLocality, includeOnlyPostalCode} = {}) { 3 | this.result = []; 4 | 5 | this.search = search; 6 | this.addressId = addressId; 7 | this.country = country; 8 | this.maxResults = maxResults; 9 | this.includeOnlyLocality = includeOnlyLocality; 10 | this.includeOnlyPostalCode = includeOnlyPostalCode; 11 | this.customParameters = {}; 12 | } 13 | 14 | addCustomParameter(key, value) { 15 | this.customParameters[key] = value; 16 | } 17 | } 18 | 19 | module.exports = Lookup; -------------------------------------------------------------------------------- /src/international_address_autocomplete/Suggestion.js: -------------------------------------------------------------------------------- 1 | class Suggestion { 2 | constructor(responseData) { 3 | this.street = responseData.street; 4 | this.locality = responseData.locality; 5 | this.administrativeArea = responseData.administrative_area; 6 | this.administrativeAreaShort = responseData.administrative_area_short; 7 | this.administrativeAreaLong = responseData.administrative_area_long; 8 | this.postalCode = responseData.postal_code; 9 | this.countryIso3 = responseData.country_iso3; 10 | this.entries = responseData.entries; 11 | this.addressText = responseData.address_text; 12 | this.addressId = responseData.address_id; 13 | } 14 | } 15 | 16 | module.exports = Suggestion; -------------------------------------------------------------------------------- /src/international_street/Candidate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A candidate is a possible match for an address that was submitted.
3 | * A lookup can have multiple candidates if the address was ambiguous. 4 | * 5 | * @see "https://www.smarty.com/docs/cloud/international-street-api#root" 6 | */ 7 | class Candidate { 8 | constructor(responseData) { 9 | this.organization = responseData.organization; 10 | this.address1 = responseData.address1; 11 | this.address2 = responseData.address2; 12 | this.address3 = responseData.address3; 13 | this.address4 = responseData.address4; 14 | this.address5 = responseData.address5; 15 | this.address6 = responseData.address6; 16 | this.address7 = responseData.address7; 17 | this.address8 = responseData.address8; 18 | this.address9 = responseData.address9; 19 | this.address10 = responseData.address10; 20 | this.address11 = responseData.address11; 21 | this.address12 = responseData.address12; 22 | 23 | this.components = {}; 24 | if (responseData.components !== undefined) { 25 | this.components.countryIso3 = responseData.components.country_iso_3; 26 | this.components.superAdministrativeArea = responseData.components.super_administrative_area; 27 | this.components.administrativeArea = responseData.components.administrative_area; 28 | this.components.administrativeAreaIso2 = responseData.components.administrative_area_iso2; 29 | this.components.administrativeAreaShort = responseData.components.administrative_area_short; 30 | this.components.administrativeAreaLong = responseData.components.administrative_area_long; 31 | this.components.subAdministrativeArea = responseData.components.sub_administrative_area; 32 | this.components.dependentLocality = responseData.components.dependent_locality; 33 | this.components.dependentLocalityName = responseData.components.dependent_locality_name; 34 | this.components.doubleDependentLocality = responseData.components.double_dependent_locality; 35 | this.components.locality = responseData.components.locality; 36 | this.components.postalCode = responseData.components.postal_code; 37 | this.components.postalCodeShort = responseData.components.postal_code_short; 38 | this.components.postalCodeExtra = responseData.components.postal_code_extra; 39 | this.components.premise = responseData.components.premise; 40 | this.components.premiseExtra = responseData.components.premise_extra; 41 | this.components.premisePrefixNumber = responseData.components.premise_prefix_number; 42 | this.components.premiseNumber = responseData.components.premise_number; 43 | this.components.premiseType = responseData.components.premise_type; 44 | this.components.thoroughfare = responseData.components.thoroughfare; 45 | this.components.thoroughfarePredirection = responseData.components.thoroughfare_predirection; 46 | this.components.thoroughfarePostdirection = responseData.components.thoroughfare_postdirection; 47 | this.components.thoroughfareName = responseData.components.thoroughfare_name; 48 | this.components.thoroughfareTrailingType = responseData.components.thoroughfare_trailing_type; 49 | this.components.thoroughfareType = responseData.components.thoroughfare_type; 50 | this.components.dependentThoroughfare = responseData.components.dependent_thoroughfare; 51 | this.components.dependentThoroughfarePredirection = responseData.components.dependent_thoroughfare_predirection; 52 | this.components.dependentThoroughfarePostdirection = responseData.components.dependent_thoroughfare_postdirection; 53 | this.components.dependentThoroughfareName = responseData.components.dependent_thoroughfare_name; 54 | this.components.dependentThoroughfareTrailingType = responseData.components.dependent_thoroughfare_trailing_type; 55 | this.components.dependentThoroughfareType = responseData.components.dependent_thoroughfare_type; 56 | this.components.building = responseData.components.building; 57 | this.components.buildingLeadingType = responseData.components.building_leading_type; 58 | this.components.buildingName = responseData.components.building_name; 59 | this.components.buildingTrailingType = responseData.components.building_trailing_type; 60 | this.components.subBuildingType = responseData.components.sub_building_type; 61 | this.components.subBuildingNumber = responseData.components.sub_building_number; 62 | this.components.subBuildingName = responseData.components.sub_building_name; 63 | this.components.subBuilding = responseData.components.sub_building; 64 | this.components.levelType = responseData.components.level_type; 65 | this.components.levelNumber = responseData.components.level_number; 66 | this.components.postBox = responseData.components.post_box; 67 | this.components.postBoxType = responseData.components.post_box_type; 68 | this.components.postBoxNumber = responseData.components.post_box_number; 69 | this.components.additionalContent = responseData.components.additional_content; 70 | this.components.deliveryInstallation = responseData.components.delivery_installation; 71 | this.components.deliveryInstallationType = responseData.components.delivery_installation_type; 72 | this.components.deliveryInstallationQualifierName = responseData.components.delivery_installation_qualifier_name; 73 | this.components.route = responseData.components.route; 74 | this.components.routeNumber = responseData.components.route_number; 75 | this.components.routeType = responseData.components.route_type; 76 | } 77 | 78 | this.analysis = {}; 79 | if (responseData.analysis !== undefined) { 80 | this.analysis.verificationStatus = responseData.analysis.verification_status; 81 | this.analysis.addressPrecision = responseData.analysis.address_precision; 82 | this.analysis.maxAddressPrecision = responseData.analysis.max_address_precision; 83 | 84 | this.analysis.changes = {}; 85 | if (responseData.analysis.changes !== undefined) { 86 | this.analysis.changes.organization = responseData.analysis.changes.organization; 87 | this.analysis.changes.address1 = responseData.analysis.changes.address1; 88 | this.analysis.changes.address2 = responseData.analysis.changes.address2; 89 | this.analysis.changes.address3 = responseData.analysis.changes.address3; 90 | this.analysis.changes.address4 = responseData.analysis.changes.address4; 91 | this.analysis.changes.address5 = responseData.analysis.changes.address5; 92 | this.analysis.changes.address6 = responseData.analysis.changes.address6; 93 | this.analysis.changes.address7 = responseData.analysis.changes.address7; 94 | this.analysis.changes.address8 = responseData.analysis.changes.address8; 95 | this.analysis.changes.address9 = responseData.analysis.changes.address9; 96 | this.analysis.changes.address10 = responseData.analysis.changes.address10; 97 | this.analysis.changes.address11 = responseData.analysis.changes.address11; 98 | this.analysis.changes.address12 = responseData.analysis.changes.address12; 99 | 100 | this.analysis.changes.components = {}; 101 | if (responseData.analysis.changes.components !== undefined) { 102 | this.analysis.changes.components.countryIso3 = responseData.analysis.changes.components.country_iso_3; 103 | this.analysis.changes.components.superAdministrativeArea = responseData.analysis.changes.components.super_administrative_area; 104 | this.analysis.changes.components.administrativeArea = responseData.analysis.changes.components.administrative_area; 105 | this.analysis.changes.components.administrativeAreaIso2 = responseData.analysis.changes.components.administrative_area_iso2; 106 | this.analysis.changes.components.administrativeAreaShort = responseData.analysis.changes.components.administrative_area_short; 107 | this.analysis.changes.components.administrativeAreaLong = responseData.analysis.changes.components.administrative_area_long; 108 | this.analysis.changes.components.subAdministrativeArea = responseData.analysis.changes.components.sub_administrative_area; 109 | this.analysis.changes.components.dependentLocality = responseData.analysis.changes.components.dependent_locality; 110 | this.analysis.changes.components.dependentLocalityName = responseData.analysis.changes.components.dependent_locality_name; 111 | this.analysis.changes.components.doubleDependentLocality = responseData.analysis.changes.components.double_dependent_locality; 112 | this.analysis.changes.components.locality = responseData.analysis.changes.components.locality; 113 | this.analysis.changes.components.postalCode = responseData.analysis.changes.components.postal_code; 114 | this.analysis.changes.components.postalCodeShort = responseData.analysis.changes.components.postal_code_short; 115 | this.analysis.changes.components.postalCodeExtra = responseData.analysis.changes.components.postal_code_extra; 116 | this.analysis.changes.components.premise = responseData.analysis.changes.components.premise; 117 | this.analysis.changes.components.premiseExtra = responseData.analysis.changes.components.premise_extra; 118 | this.analysis.changes.components.premisePrefixNumber = responseData.analysis.changes.components.premise_prefix_number; 119 | this.analysis.changes.components.premiseNumber = responseData.analysis.changes.components.premise_number; 120 | this.analysis.changes.components.premiseType = responseData.analysis.changes.components.premise_type; 121 | this.analysis.changes.components.thoroughfare = responseData.analysis.changes.components.thoroughfare; 122 | this.analysis.changes.components.thoroughfarePredirection = responseData.analysis.changes.components.thoroughfare_predirection; 123 | this.analysis.changes.components.thoroughfarePostdirection = responseData.analysis.changes.components.thoroughfare_postdirection; 124 | this.analysis.changes.components.thoroughfareName = responseData.analysis.changes.components.thoroughfare_name; 125 | this.analysis.changes.components.thoroughfareTrailingType = responseData.analysis.changes.components.thoroughfare_trailing_type; 126 | this.analysis.changes.components.thoroughfareType = responseData.analysis.changes.components.thoroughfare_type; 127 | this.analysis.changes.components.dependentThoroughfare = responseData.analysis.changes.components.dependent_thoroughfare; 128 | this.analysis.changes.components.dependentThoroughfarePredirection = responseData.analysis.changes.components.dependent_thoroughfare_predirection; 129 | this.analysis.changes.components.dependentThoroughfarePostdirection = responseData.analysis.changes.components.dependent_thoroughfare_postdirection; 130 | this.analysis.changes.components.dependentThoroughfareName = responseData.analysis.changes.components.dependent_thoroughfare_name; 131 | this.analysis.changes.components.dependentThoroughfareTrailingType = responseData.analysis.changes.components.dependent_thoroughfare_trailing_type; 132 | this.analysis.changes.components.dependentThoroughfareType = responseData.analysis.changes.components.dependent_thoroughfare_type; 133 | this.analysis.changes.components.building = responseData.analysis.changes.components.building; 134 | this.analysis.changes.components.buildingLeadingType = responseData.analysis.changes.components.building_leading_type; 135 | this.analysis.changes.components.buildingName = responseData.analysis.changes.components.building_name; 136 | this.analysis.changes.components.buildingTrailingType = responseData.analysis.changes.components.building_trailing_type; 137 | this.analysis.changes.components.subBuildingType = responseData.analysis.changes.components.sub_building_type; 138 | this.analysis.changes.components.subBuildingNumber = responseData.analysis.changes.components.sub_building_number; 139 | this.analysis.changes.components.subBuildingName = responseData.analysis.changes.components.sub_building_name; 140 | this.analysis.changes.components.subBuilding = responseData.analysis.changes.components.sub_building; 141 | this.analysis.changes.components.levelType = responseData.analysis.changes.components.level_type; 142 | this.analysis.changes.components.levelNumber = responseData.analysis.changes.components.level_number; 143 | this.analysis.changes.components.postBox = responseData.analysis.changes.components.post_box; 144 | this.analysis.changes.components.postBoxType = responseData.analysis.changes.components.post_box_type; 145 | this.analysis.changes.components.postBoxNumber = responseData.analysis.changes.components.post_box_number; 146 | } 147 | //TODO: Fill in the rest of these fields and their corresponding tests. 148 | } 149 | } 150 | 151 | this.metadata = {}; 152 | if (responseData.metadata !== undefined) { 153 | this.metadata.latitude = responseData.metadata.latitude; 154 | this.metadata.longitude = responseData.metadata.longitude; 155 | this.metadata.geocodePrecision = responseData.metadata.geocode_precision; 156 | this.metadata.maxGeocodePrecision = responseData.metadata.max_geocode_precision; 157 | this.metadata.addressFormat = responseData.metadata.address_format; 158 | } 159 | } 160 | } 161 | 162 | module.exports = Candidate; -------------------------------------------------------------------------------- /src/international_street/Client.js: -------------------------------------------------------------------------------- 1 | const Request = require("../Request"); 2 | const Errors = require("../Errors"); 3 | const Candidate = require("./Candidate"); 4 | const buildInputData = require("../util/buildInputData"); 5 | const keyTranslationFormat = require("../util/apiToSDKKeyMap").internationalStreet; 6 | 7 | /** 8 | * This client sends lookups to the Smarty International Street API,
9 | * and attaches the results to the appropriate Lookup objects. 10 | */ 11 | class Client { 12 | constructor(sender) { 13 | this.sender = sender; 14 | } 15 | 16 | send(lookup) { 17 | if (typeof lookup === "undefined") throw new Errors.UndefinedLookupError(); 18 | 19 | let request = new Request(); 20 | request.parameters = buildInputData(lookup, keyTranslationFormat); 21 | 22 | return new Promise((resolve, reject) => { 23 | this.sender.send(request) 24 | .then(response => { 25 | if (response.error) reject(response.error); 26 | 27 | resolve(attachLookupCandidates(response, lookup)); 28 | }) 29 | .catch(reject); 30 | }); 31 | 32 | function attachLookupCandidates(response, lookup) { 33 | response.payload.map(rawCandidate => { 34 | lookup.result.push(new Candidate(rawCandidate)); 35 | }); 36 | 37 | return lookup; 38 | } 39 | } 40 | } 41 | 42 | module.exports = Client; -------------------------------------------------------------------------------- /src/international_street/Lookup.js: -------------------------------------------------------------------------------- 1 | const UnprocessableEntityError = require("../Errors").UnprocessableEntityError; 2 | const messages = { 3 | countryRequired: "Country field is required.", 4 | freeformOrAddress1Required: "Either freeform or address1 is required.", 5 | insufficientInformation: "Insufficient information: One or more required fields were not set on the lookup.", 6 | badGeocode: "Invalid input: geocode can only be set to 'true' (default is 'false'.", 7 | invalidLanguage: "Invalid input: language can only be set to 'latin' or 'native'. When not set, the the output language will match the language of the input values." 8 | }; 9 | 10 | 11 | /** 12 | * In addition to holding all of the input data for this lookup, this class also
13 | * will contain the result of the lookup after it comes back from the API. 14 | *

Note: Lookups must have certain required fields set with non-blank values.
15 | * These can be found at the URL below.

16 | * @see "https://www.smarty.com/docs/cloud/international-street-api#http-input-fields" 17 | */ 18 | class Lookup { 19 | constructor(country, freeform) { 20 | this.result = []; 21 | 22 | this.country = country; 23 | this.freeform = freeform; 24 | this.address1 = undefined; 25 | this.address2 = undefined; 26 | this.address3 = undefined; 27 | this.address4 = undefined; 28 | this.organization = undefined; 29 | this.locality = undefined; 30 | this.administrativeArea = undefined; 31 | this.postalCode = undefined; 32 | this.geocode = undefined; 33 | this.language = undefined; 34 | this.inputId = undefined; 35 | 36 | this.ensureEnoughInfo = this.ensureEnoughInfo.bind(this); 37 | this.ensureValidData = this.ensureValidData.bind(this); 38 | this.customParameters = {}; 39 | } 40 | 41 | addCustomParameter(key, value) { 42 | this.customParameters[key] = value; 43 | } 44 | 45 | ensureEnoughInfo() { 46 | if (fieldIsMissing(this.country)) throw new UnprocessableEntityError(messages.countryRequired); 47 | 48 | if (fieldIsSet(this.freeform)) return true; 49 | 50 | if (fieldIsMissing(this.address1)) throw new UnprocessableEntityError(messages.freeformOrAddress1Required); 51 | 52 | if (fieldIsSet(this.postalCode)) return true; 53 | 54 | if (fieldIsMissing(this.locality) || fieldIsMissing(this.administrativeArea)) throw new UnprocessableEntityError(messages.insufficientInformation); 55 | 56 | return true; 57 | } 58 | 59 | ensureValidData() { 60 | let languageIsSetIncorrectly = () => { 61 | let isLanguage = language => this.language.toLowerCase() === language; 62 | 63 | return fieldIsSet(this.language) && !(isLanguage("latin") || isLanguage("native")); 64 | }; 65 | 66 | let geocodeIsSetIncorrectly = () => { 67 | return fieldIsSet(this.geocode) && this.geocode.toLowerCase() !== "true"; 68 | }; 69 | 70 | if (geocodeIsSetIncorrectly()) throw new UnprocessableEntityError(messages.badGeocode); 71 | 72 | if (languageIsSetIncorrectly()) throw new UnprocessableEntityError(messages.invalidLanguage); 73 | 74 | return true; 75 | } 76 | } 77 | 78 | function fieldIsMissing (field) { 79 | if (!field) return true; 80 | 81 | const whitespaceCharacters = /\s/g; 82 | 83 | return field.replace(whitespaceCharacters, "").length < 1; 84 | } 85 | 86 | function fieldIsSet (field) { 87 | return !fieldIsMissing(field); 88 | } 89 | 90 | module.exports = Lookup; -------------------------------------------------------------------------------- /src/us_autocomplete_pro/Client.js: -------------------------------------------------------------------------------- 1 | const Errors = require("../Errors"); 2 | const Request = require("../Request"); 3 | const Suggestion = require("./Suggestion"); 4 | const buildInputData = require("../util/buildInputData"); 5 | const keyTranslationFormat = require("../util/apiToSDKKeyMap").usAutocompletePro; 6 | 7 | /** 8 | * This client sends lookups to the Smarty US Autocomplete Pro API,
9 | * and attaches the suggestions to the appropriate Lookup objects. 10 | */ 11 | class Client { 12 | constructor(sender) { 13 | this.sender = sender; 14 | } 15 | 16 | send(lookup) { 17 | if (typeof lookup === "undefined") throw new Errors.UndefinedLookupError(); 18 | 19 | let request = new Request(); 20 | request.parameters = buildInputData(lookup, keyTranslationFormat); 21 | 22 | return new Promise((resolve, reject) => { 23 | this.sender.send(request) 24 | .then(response => { 25 | if (response.error) reject(response.error); 26 | 27 | lookup.result = buildSuggestionsFromResponse(response.payload); 28 | resolve(lookup); 29 | }) 30 | .catch(reject); 31 | }); 32 | 33 | function buildSuggestionsFromResponse(payload) { 34 | if (payload.suggestions === null) return []; 35 | 36 | return payload.suggestions.map(suggestion => new Suggestion(suggestion)); 37 | } 38 | } 39 | } 40 | 41 | module.exports = Client; -------------------------------------------------------------------------------- /src/us_autocomplete_pro/Lookup.js: -------------------------------------------------------------------------------- 1 | /** 2 | * In addition to holding all of the input data for this lookup, this class also
3 | * will contain the result of the lookup after it comes back from the API. 4 | * @see "https://www.smarty.com/docs/cloud/us-autocomplete-api#pro-http-request-input-fields" 5 | */ 6 | class Lookup { 7 | /** 8 | * @param search The beginning of an address. This is required to be set. 9 | */ 10 | constructor(search) { 11 | this.result = []; 12 | 13 | this.search = search; 14 | this.selected = undefined; 15 | this.maxResults = undefined; 16 | this.includeOnlyCities = []; 17 | this.includeOnlyStates = []; 18 | this.includeOnlyZIPCodes = []; 19 | this.excludeStates = []; 20 | this.preferCities = []; 21 | this.preferStates = []; 22 | this.preferZIPCodes = []; 23 | this.preferRatio = undefined; 24 | this.preferGeolocation = undefined; 25 | this.source = undefined; 26 | this.customParameters = {}; 27 | } 28 | 29 | addCustomParameter(key, value) { 30 | this.customParameters[key] = value; 31 | } 32 | } 33 | 34 | module.exports = Lookup; -------------------------------------------------------------------------------- /src/us_autocomplete_pro/Suggestion.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @see "https://www.smarty.com/docs/cloud/us-autocomplete-api#pro-http-response" 3 | */ 4 | class Suggestion { 5 | constructor(responseData) { 6 | this.streetLine = responseData.street_line; 7 | this.secondary = responseData.secondary; 8 | this.city = responseData.city; 9 | this.state = responseData.state; 10 | this.zipcode = responseData.zipcode; 11 | this.entries = responseData.entries; 12 | 13 | if (responseData.source) { 14 | this.source = responseData.source; 15 | } 16 | } 17 | } 18 | 19 | module.exports = Suggestion; -------------------------------------------------------------------------------- /src/us_enrichment/Client.js: -------------------------------------------------------------------------------- 1 | const Errors = require("../Errors"); 2 | const Request = require("../Request"); 3 | const buildInputData = require("../util/buildInputData"); 4 | const {usEnrichment: keyTranslationFormat} = require("../util/apiToSDKKeyMap"); 5 | 6 | class Client { 7 | constructor(sender) { 8 | this.sender = sender; 9 | } 10 | 11 | sendPrincipal(lookup) { 12 | if (typeof lookup === "undefined") throw new Errors.UndefinedLookupError(); 13 | 14 | let request = new Request(); 15 | request.parameters = buildInputData(lookup, keyTranslationFormat); 16 | 17 | request.baseUrlParam = lookup.smartyKey + "/property/principal"; 18 | 19 | return new Promise((resolve, reject) => { 20 | this.sender.send(request) 21 | .then(response => { 22 | if (response.error) reject(response.error); 23 | 24 | lookup.response = response.payload; 25 | resolve(lookup); 26 | }) 27 | .catch(reject); 28 | }); 29 | } 30 | 31 | sendFinancial(lookup) { 32 | if (typeof lookup === "undefined") throw new Errors.UndefinedLookupError(); 33 | 34 | let request = new Request(); 35 | request.parameters = buildInputData(lookup, keyTranslationFormat); 36 | 37 | request.baseUrlParam = lookup.smartyKey + "/property/financial"; 38 | 39 | return new Promise((resolve, reject) => { 40 | this.sender.send(request) 41 | .then(response => { 42 | if (response.error) reject(response.error); 43 | 44 | lookup.response = response.payload; 45 | resolve(lookup); 46 | }) 47 | .catch(reject); 48 | }); 49 | } 50 | 51 | sendGeo(lookup) { 52 | if (typeof lookup === "undefined") throw new Errors.UndefinedLookupError(); 53 | 54 | let request = new Request(); 55 | request.parameters = buildInputData(lookup, keyTranslationFormat); 56 | 57 | request.baseUrlParam = lookup.smartyKey + "/geo-reference"; 58 | 59 | return new Promise((resolve, reject) => { 60 | this.sender.send(request) 61 | .then(response => { 62 | if (response.error) reject(response.error); 63 | 64 | lookup.response = response.payload; 65 | resolve(lookup); 66 | }) 67 | .catch(reject); 68 | }); 69 | } 70 | 71 | sendSecondary(lookup) { 72 | if (typeof lookup === "undefined") throw new Errors.UndefinedLookupError(); 73 | 74 | let request = new Request(); 75 | request.parameters = buildInputData(lookup, keyTranslationFormat); 76 | 77 | request.baseUrlParam = lookup.smartyKey + "/secondary"; 78 | 79 | return new Promise((resolve, reject) => { 80 | this.sender.send(request) 81 | .then(response => { 82 | if (response.error) reject(response.error); 83 | 84 | lookup.response = response.payload; 85 | resolve(lookup); 86 | }) 87 | .catch(reject); 88 | }); 89 | } 90 | 91 | sendSecondaryCount(lookup) { 92 | if (typeof lookup === "undefined") throw new Errors.UndefinedLookupError(); 93 | 94 | let request = new Request(); 95 | request.parameters = buildInputData(lookup, keyTranslationFormat); 96 | 97 | request.baseUrlParam = lookup.smartyKey + "/secondary/count"; 98 | 99 | return new Promise((resolve, reject) => { 100 | this.sender.send(request) 101 | .then(response => { 102 | if (response.error) reject(response.error); 103 | 104 | lookup.response = response.payload; 105 | resolve(lookup); 106 | }) 107 | .catch(reject); 108 | }); 109 | } 110 | } 111 | 112 | module.exports = Client; -------------------------------------------------------------------------------- /src/us_enrichment/Lookup.js: -------------------------------------------------------------------------------- 1 | class Lookup { 2 | constructor(smartyKey, include, exclude, dataset, dataSubset) { 3 | this.smartyKey = smartyKey; 4 | this.include = include; 5 | this.exclude = exclude; 6 | this.dataset = dataset; 7 | this.dataSubset = dataSubset; 8 | 9 | this.response = {}; 10 | this.customParameters = {}; 11 | }; 12 | 13 | addCustomParameter(key, value) { 14 | this.customParameters[key] = value; 15 | } 16 | } 17 | 18 | module.exports = Lookup; -------------------------------------------------------------------------------- /src/us_extract/Address.js: -------------------------------------------------------------------------------- 1 | const Candidate = require("../us_street/Candidate"); 2 | 3 | /** 4 | * @see Smarty US Extract API docs 5 | */ 6 | class Address { 7 | constructor (responseData) { 8 | this.text = responseData.text; 9 | this.verified = responseData.verified; 10 | this.line = responseData.line; 11 | this.start = responseData.start; 12 | this.end = responseData.end; 13 | this.candidates = responseData.api_output.map(rawAddress => new Candidate(rawAddress)); 14 | } 15 | } 16 | 17 | module.exports = Address; -------------------------------------------------------------------------------- /src/us_extract/Client.js: -------------------------------------------------------------------------------- 1 | const Errors = require("../Errors"); 2 | const Request = require("../Request"); 3 | const Result = require("./Result"); 4 | const buildInputData = require("../util/buildInputData"); 5 | const keyTranslationFormat = require("../util/apiToSDKKeyMap").usExtract; 6 | 7 | /** 8 | * This client sends lookups to the Smarty US Extract API,
9 | * and attaches the results to the Lookup objects. 10 | */ 11 | class Client { 12 | constructor(sender) { 13 | this.sender = sender; 14 | } 15 | 16 | send(lookup) { 17 | if (typeof lookup === "undefined") throw new Errors.UndefinedLookupError(); 18 | 19 | let request = new Request(lookup.text, {"Content-Type": "text/plain; charset=utf-8"}); 20 | request.parameters = buildInputData(lookup, keyTranslationFormat); 21 | 22 | return new Promise((resolve, reject) => { 23 | this.sender.send(request) 24 | .then(response => { 25 | if (response.error) reject(response.error); 26 | 27 | lookup.result = new Result(response.payload); 28 | resolve(lookup); 29 | }) 30 | .catch(reject); 31 | }); 32 | } 33 | } 34 | 35 | module.exports = Client; -------------------------------------------------------------------------------- /src/us_extract/Lookup.js: -------------------------------------------------------------------------------- 1 | /** 2 | * In addition to holding all of the input data for this lookup, this class also
3 | * will contain the result of the lookup after it comes back from the API. 4 | * @see "https://www.smarty.com/docs/cloud/us-extract-api#http-request-input-fields" 5 | */ 6 | class Lookup { 7 | /** 8 | * @param text The text that is to have addresses extracted out of it for verification (required) 9 | */ 10 | constructor(text) { 11 | this.result = { 12 | meta: {}, 13 | addresses: [], 14 | }; 15 | //TODO: require the text field. 16 | this.text = text; 17 | this.html = undefined; 18 | this.aggressive = undefined; 19 | this.addressesHaveLineBreaks = undefined; 20 | this.addressesPerLine = undefined; 21 | this.customParameters = {}; 22 | } 23 | 24 | addCustomParameter(key, value) { 25 | this.customParameters[key] = value; 26 | } 27 | } 28 | 29 | module.exports = Lookup; -------------------------------------------------------------------------------- /src/us_extract/Result.js: -------------------------------------------------------------------------------- 1 | const Address = require("./Address"); 2 | 3 | /** 4 | * @see Smarty US Extract API docs 5 | */ 6 | class Result { 7 | constructor({meta, addresses}) { 8 | this.meta = { 9 | lines: meta.lines, 10 | unicode: meta.unicode, 11 | addressCount: meta.address_count, 12 | verifiedCount: meta.verified_count, 13 | bytes: meta.bytes, 14 | characterCount: meta.character_count, 15 | }; 16 | 17 | this.addresses = addresses.map(rawAddress => new Address(rawAddress)); 18 | } 19 | } 20 | 21 | module.exports = Result; -------------------------------------------------------------------------------- /src/us_reverse_geo/Client.js: -------------------------------------------------------------------------------- 1 | const Request = require("../Request"); 2 | const Response = require("./Response"); 3 | const buildInputData = require("../util/buildInputData"); 4 | const keyTranslationFormat = require("../util/apiToSDKKeyMap").usReverseGeo; 5 | const {UndefinedLookupError} = require("../Errors.js"); 6 | 7 | /** 8 | * This client sends lookups to the Smarty US Reverse Geo API,
9 | * and attaches the results to the appropriate Lookup objects. 10 | */ 11 | class Client { 12 | constructor(sender) { 13 | this.sender = sender; 14 | } 15 | 16 | send(lookup) { 17 | if (typeof lookup === "undefined") throw new UndefinedLookupError(); 18 | 19 | let request = new Request(); 20 | request.parameters = buildInputData(lookup, keyTranslationFormat); 21 | 22 | return new Promise((resolve, reject) => { 23 | this.sender.send(request) 24 | .then(response => { 25 | if (response.error) reject(response.error); 26 | 27 | resolve(attachLookupResults(response, lookup)); 28 | }) 29 | .catch(reject); 30 | }); 31 | 32 | function attachLookupResults(response, lookup) { 33 | lookup.response = new Response(response.payload); 34 | 35 | return lookup; 36 | } 37 | } 38 | } 39 | 40 | module.exports = Client; 41 | -------------------------------------------------------------------------------- /src/us_reverse_geo/Lookup.js: -------------------------------------------------------------------------------- 1 | const Response = require("./Response"); 2 | 3 | /** 4 | * In addition to holding all of the input data for this lookup, this class also
5 | * will contain the result of the lookup after it comes back from the API. 6 | * @see "https://www.smarty.com/docs/cloud/us-street-api#input-fields" 7 | */ 8 | class Lookup { 9 | constructor(latitude, longitude, source="") { 10 | this.latitude = latitude.toFixed(8); 11 | this.longitude = longitude.toFixed(8); 12 | this.source = source; 13 | this.response = new Response(); 14 | this.customParameters = {}; 15 | } 16 | 17 | addCustomParameter(key, value) { 18 | this.customParameters[key] = value; 19 | } 20 | } 21 | 22 | module.exports = Lookup; 23 | -------------------------------------------------------------------------------- /src/us_reverse_geo/Response.js: -------------------------------------------------------------------------------- 1 | const Result = require("./Result"); 2 | 3 | /** 4 | * The SmartyResponse contains the response from a call to the US Reverse Geo API. 5 | */ 6 | class Response { 7 | constructor(responseData) { 8 | this.results = []; 9 | 10 | if (responseData) 11 | responseData.results.map(rawResult => { 12 | this.results.push(new Result(rawResult)); 13 | }); 14 | } 15 | } 16 | 17 | module.exports = Response; 18 | -------------------------------------------------------------------------------- /src/us_reverse_geo/Result.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A candidate is a possible match for an address that was submitted.
3 | * A lookup can have multiple candidates if the address was ambiguous. 4 | * 5 | * @see "https://www.smarty.com/docs/cloud/us-reverse-geo-api#result" 6 | */ 7 | class Result { 8 | constructor(responseData) { 9 | this.distance = responseData.distance; 10 | 11 | this.address = {}; 12 | if (responseData.address) { 13 | this.address.street = responseData.address.street; 14 | this.address.city = responseData.address.city; 15 | this.address.state_abbreviation = responseData.address.state_abbreviation; 16 | this.address.zipcode = responseData.address.zipcode; 17 | this.address.source = responseData.address.source 18 | } 19 | 20 | this.coordinate = {}; 21 | if (responseData.coordinate) { 22 | this.coordinate.latitude = responseData.coordinate.latitude; 23 | this.coordinate.longitude = responseData.coordinate.longitude; 24 | this.coordinate.accuracy = responseData.coordinate.accuracy; 25 | switch (responseData.coordinate.license) { 26 | case 1: 27 | this.coordinate.license = "SmartyStreets Proprietary"; 28 | break; 29 | default: 30 | this.coordinate.license = "SmartyStreets"; 31 | } 32 | } 33 | } 34 | } 35 | 36 | module.exports = Result; -------------------------------------------------------------------------------- /src/us_street/Candidate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A candidate is a possible match for an address that was submitted.
3 | * A lookup can have multiple candidates if the address was ambiguous, and
4 | * the maxCandidates field is set higher than 1. 5 | * 6 | * @see "https://www.smarty.com/docs/cloud/us-street-api#root" 7 | */ 8 | class Candidate { 9 | constructor(responseData) { 10 | this.inputIndex = responseData.input_index; 11 | this.candidateIndex = responseData.candidate_index; 12 | this.addressee = responseData.addressee; 13 | this.deliveryLine1 = responseData.delivery_line_1; 14 | this.deliveryLine2 = responseData.delivery_line_2; 15 | this.lastLine = responseData.last_line; 16 | this.deliveryPointBarcode = responseData.delivery_point_barcode; 17 | this.smartyKey = responseData.smarty_key; 18 | 19 | this.components = {}; 20 | if (responseData.components !== undefined) { 21 | this.components.urbanization = responseData.components.urbanization; 22 | this.components.primaryNumber = responseData.components.primary_number; 23 | this.components.streetName = responseData.components.street_name; 24 | this.components.streetPredirection = responseData.components.street_predirection; 25 | this.components.streetPostdirection = responseData.components.street_postdirection; 26 | this.components.streetSuffix = responseData.components.street_suffix; 27 | this.components.secondaryNumber = responseData.components.secondary_number; 28 | this.components.secondaryDesignator = responseData.components.secondary_designator; 29 | this.components.extraSecondaryNumber = responseData.components.extra_secondary_number; 30 | this.components.extraSecondaryDesignator = responseData.components.extra_secondary_designator; 31 | this.components.pmbDesignator = responseData.components.pmb_designator; 32 | this.components.pmbNumber = responseData.components.pmb_number; 33 | this.components.cityName = responseData.components.city_name; 34 | this.components.defaultCityName = responseData.components.default_city_name; 35 | this.components.state = responseData.components.state_abbreviation; 36 | this.components.zipCode = responseData.components.zipcode; 37 | this.components.plus4Code = responseData.components.plus4_code; 38 | this.components.deliveryPoint = responseData.components.delivery_point; 39 | this.components.deliveryPointCheckDigit = responseData.components.delivery_point_check_digit; 40 | } 41 | 42 | this.metadata = {}; 43 | if (responseData.metadata !== undefined) { 44 | this.metadata.recordType = responseData.metadata.record_type; 45 | this.metadata.zipType = responseData.metadata.zip_type; 46 | this.metadata.countyFips = responseData.metadata.county_fips; 47 | this.metadata.countyName = responseData.metadata.county_name; 48 | this.metadata.carrierRoute = responseData.metadata.carrier_route; 49 | this.metadata.congressionalDistrict = responseData.metadata.congressional_district; 50 | this.metadata.buildingDefaultIndicator = responseData.metadata.building_default_indicator; 51 | this.metadata.rdi = responseData.metadata.rdi; 52 | this.metadata.elotSequence = responseData.metadata.elot_sequence; 53 | this.metadata.elotSort = responseData.metadata.elot_sort; 54 | this.metadata.latitude = responseData.metadata.latitude; 55 | this.metadata.longitude = responseData.metadata.longitude; 56 | switch (responseData.metadata.coordinate_license) 57 | { 58 | case 1: 59 | this.metadata.coordinateLicense = "SmartyStreets Proprietary"; 60 | break; 61 | default: 62 | this.metadata.coordinateLicense = "SmartyStreets"; 63 | } 64 | this.metadata.precision = responseData.metadata.precision; 65 | this.metadata.timeZone = responseData.metadata.time_zone; 66 | this.metadata.utcOffset = responseData.metadata.utc_offset; 67 | this.metadata.obeysDst = responseData.metadata.dst; 68 | this.metadata.isEwsMatch = responseData.metadata.ews_match; 69 | } 70 | 71 | this.analysis = {}; 72 | if (responseData.analysis !== undefined) { 73 | this.analysis.dpvMatchCode = responseData.analysis.dpv_match_code; 74 | this.analysis.dpvFootnotes = responseData.analysis.dpv_footnotes; 75 | this.analysis.cmra = responseData.analysis.dpv_cmra; 76 | this.analysis.vacant = responseData.analysis.dpv_vacant; 77 | this.analysis.noStat = responseData.analysis.dpv_no_stat; 78 | this.analysis.active = responseData.analysis.active; 79 | this.analysis.isEwsMatch = responseData.analysis.ews_match; // Deprecated, refer to metadata.ews_match 80 | this.analysis.footnotes = responseData.analysis.footnotes; 81 | this.analysis.lacsLinkCode = responseData.analysis.lacslink_code; 82 | this.analysis.lacsLinkIndicator = responseData.analysis.lacslink_indicator; 83 | this.analysis.isSuiteLinkMatch = responseData.analysis.suitelink_match; 84 | this.analysis.enhancedMatch = responseData.analysis.enhanced_match; 85 | } 86 | } 87 | } 88 | 89 | module.exports = Candidate; -------------------------------------------------------------------------------- /src/us_street/Client.js: -------------------------------------------------------------------------------- 1 | const Candidate = require("./Candidate"); 2 | const Lookup = require("./Lookup"); 3 | const Batch = require("../Batch"); 4 | const UndefinedLookupError = require("../Errors").UndefinedLookupError; 5 | const sendBatch = require("../util/sendBatch"); 6 | const keyTranslationFormat = require("../util/apiToSDKKeyMap").usStreet; 7 | 8 | /** 9 | * This client sends lookups to the Smarty US Street API,
10 | * and attaches the results to the appropriate Lookup objects. 11 | */ 12 | class Client { 13 | constructor(sender) { 14 | this.sender = sender; 15 | } 16 | 17 | /** 18 | * Sends up to 100 lookups for validation. 19 | * @param data may be a Lookup object, or a Batch which must contain between 1 and 100 Lookup objects 20 | * @throws SmartyException 21 | */ 22 | send(data) { 23 | const dataIsBatch = data instanceof Batch; 24 | const dataIsLookup = data instanceof Lookup; 25 | 26 | if (!dataIsLookup && !dataIsBatch) throw new UndefinedLookupError; 27 | 28 | let batch; 29 | 30 | if (dataIsLookup) { 31 | if (data.maxCandidates == null && data.match == "enhanced") 32 | data.maxCandidates = 5; 33 | batch = new Batch(); 34 | batch.add(data); 35 | } else { 36 | batch = data; 37 | } 38 | 39 | return sendBatch(batch, this.sender, Candidate, keyTranslationFormat); 40 | } 41 | } 42 | 43 | module.exports = Client; -------------------------------------------------------------------------------- /src/us_street/Lookup.js: -------------------------------------------------------------------------------- 1 | /** 2 | * In addition to holding all of the input data for this lookup, this class also
3 | * will contain the result of the lookup after it comes back from the API. 4 | * @see "https://www.smarty.com/docs/cloud/us-street-api#input-fields" 5 | */ 6 | class Lookup { 7 | constructor( 8 | street, 9 | street2, 10 | secondary, 11 | city, 12 | state, 13 | zipCode, 14 | lastLine, 15 | addressee, 16 | urbanization, 17 | match, 18 | maxCandidates, 19 | inputId, 20 | format, 21 | countySource, 22 | ) { 23 | this.street = street; 24 | this.street2 = street2; 25 | this.secondary = secondary; 26 | this.city = city; 27 | this.state = state; 28 | this.zipCode = zipCode; 29 | this.lastLine = lastLine; 30 | this.addressee = addressee; 31 | this.urbanization = urbanization; 32 | this.match = match; 33 | this.maxCandidates = maxCandidates; 34 | this.inputId = inputId; 35 | this.format = format; 36 | this.countySource = countySource; 37 | this.result = []; 38 | this.customParameters = {}; 39 | } 40 | 41 | addCustomParameter(key, value) { 42 | this.customParameters[key] = value; 43 | } 44 | 45 | } 46 | 47 | module.exports = Lookup; 48 | -------------------------------------------------------------------------------- /src/us_zipcode/Client.js: -------------------------------------------------------------------------------- 1 | const Lookup = require("./Lookup"); 2 | const Result = require("./Result"); 3 | const Batch = require("../Batch"); 4 | const UndefinedLookupError = require("../Errors").UndefinedLookupError; 5 | const sendBatch = require("../util/sendBatch"); 6 | const keyTranslationFormat = require("../util/apiToSDKKeyMap").usZipcode; 7 | 8 | /** 9 | * This client sends lookups to the Smarty US ZIP Code API,
10 | * and attaches the results to the appropriate Lookup objects. 11 | */ 12 | class Client { 13 | constructor(sender) { 14 | this.sender = sender; 15 | } 16 | 17 | /** 18 | * Sends up to 100 lookups for validation. 19 | * @param data May be a Lookup object, or a Batch which must contain between 1 and 100 Lookup objects 20 | * @throws SmartyException 21 | */ 22 | send(data) { 23 | const dataIsBatch = data instanceof Batch; 24 | const dataIsLookup = data instanceof Lookup; 25 | 26 | if (!dataIsLookup && !dataIsBatch) throw new UndefinedLookupError; 27 | 28 | let batch; 29 | 30 | if (dataIsLookup) { 31 | batch = new Batch(); 32 | batch.add(data); 33 | } else batch = data; 34 | 35 | return sendBatch(batch, this.sender, Result, keyTranslationFormat); 36 | } 37 | } 38 | 39 | module.exports = Client; -------------------------------------------------------------------------------- /src/us_zipcode/Lookup.js: -------------------------------------------------------------------------------- 1 | /** 2 | * In addition to holding all of the input data for this lookup, this class also
3 | * will contain the result of the lookup after it comes back from the API. 4 | * @see "https://www.smarty.com/docs/cloud/us-zipcode-api#http-request-input-fields" 5 | */ 6 | class Lookup { 7 | constructor(city, state, zipCode, inputId) { 8 | this.city = city; 9 | this.state = state; 10 | this.zipCode = zipCode; 11 | this.inputId = inputId; 12 | this.result = []; 13 | this.customParameters = {}; 14 | } 15 | 16 | addCustomParameter(key, value) { 17 | this.customParameters[key] = value; 18 | } 19 | } 20 | 21 | module.exports = Lookup; -------------------------------------------------------------------------------- /src/us_zipcode/Result.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @see "https://www.smarty.com/docs/cloud/us-zipcode-api#root" 3 | */ 4 | class Result { 5 | constructor(responseData) { 6 | this.inputIndex = responseData.input_index; 7 | this.status = responseData.status; 8 | this.reason = responseData.reason; 9 | this.valid = this.status === undefined && this.reason === undefined; 10 | 11 | this.cities = !responseData.city_states ? [] : responseData.city_states.map(city => { 12 | return { 13 | city: city.city, 14 | stateAbbreviation: city.state_abbreviation, 15 | state: city.state, 16 | mailableCity: city.mailable_city, 17 | }; 18 | }); 19 | 20 | this.zipcodes = !responseData.zipcodes ? [] : responseData.zipcodes.map(zipcode => { 21 | return { 22 | zipcode: zipcode.zipcode, 23 | zipcodeType: zipcode.zipcode_type, 24 | defaultCity: zipcode.default_city, 25 | countyFips: zipcode.county_fips, 26 | countyName: zipcode.county_name, 27 | latitude: zipcode.latitude, 28 | longitude: zipcode.longitude, 29 | precision: zipcode.precision, 30 | stateAbbreviation: zipcode.state_abbreviation, 31 | state: zipcode.state, 32 | alternateCounties: !zipcode.alternate_counties ? [] : zipcode.alternate_counties.map(county => { 33 | return { 34 | countyFips: county.county_fips, 35 | countyName: county.county_name, 36 | stateAbbreviation: county.state_abbreviation, 37 | state: county.state, 38 | } 39 | }), 40 | }; 41 | }); 42 | } 43 | } 44 | 45 | module.exports = Result; -------------------------------------------------------------------------------- /src/util/Sleeper.js: -------------------------------------------------------------------------------- 1 | class Sleeper { 2 | constructor () {} 3 | sleep(seconds) { 4 | return new Promise(resolve => setTimeout(resolve, seconds*1000)); 5 | } 6 | } 7 | 8 | module.exports = Sleeper; -------------------------------------------------------------------------------- /src/util/apiToSDKKeyMap.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | usStreet: { 3 | "street": "street", 4 | "street2": "street2", 5 | "secondary": "secondary", 6 | "city": "city", 7 | "state": "state", 8 | "zipcode": "zipCode", 9 | "lastline": "lastLine", 10 | "addressee": "addressee", 11 | "urbanization": "urbanization", 12 | "match": "match", 13 | "format": "format", 14 | "candidates": "maxCandidates", 15 | "county_source": "countySource" 16 | }, 17 | usAutocompletePro: { 18 | search: "search", 19 | selected: "selected", 20 | max_results: "maxResults", 21 | include_only_cities: "includeOnlyCities", 22 | include_only_states: "includeOnlyStates", 23 | include_only_zip_codes: "includeOnlyZIPCodes", 24 | exclude_states: "excludeStates", 25 | prefer_cities: "preferCities", 26 | prefer_states: "preferStates", 27 | prefer_zip_codes: "preferZIPCodes", 28 | prefer_ratio: "preferRatio", 29 | prefer_geolocation: "preferGeolocation", 30 | source: "source", 31 | }, 32 | usZipcode: { 33 | "city": "city", 34 | "state": "state", 35 | "zipcode": "zipCode", 36 | }, 37 | internationalStreet: { 38 | "country": "country", 39 | "freeform": "freeform", 40 | "address1": "address1", 41 | "address2": "address2", 42 | "address3": "address3", 43 | "address4": "address4", 44 | "organization": "organization", 45 | "locality": "locality", 46 | "administrative_area": "administrativeArea", 47 | "postal_code": "postalCode", 48 | "geocode": "geocode", 49 | "language": "language", 50 | }, 51 | internationalAddressAutocomplete: { 52 | search: "search", 53 | country: "country", 54 | max_results: "maxResults", 55 | include_only_administrative_area: "includeOnlyAdministrativeArea", 56 | include_only_locality: "includeOnlyLocality", 57 | include_only_postal_code: "includeOnlyPostalCode", 58 | }, 59 | usReverseGeo: { 60 | "latitude": "latitude", 61 | "longitude": "longitude", 62 | "source": "source" 63 | }, 64 | usExtract: { 65 | html: "html", 66 | aggressive: "aggressive", 67 | addr_line_breaks: "addressesHaveLineBreaks", 68 | addr_per_line: "addressesPerLine", 69 | }, 70 | usEnrichment: { 71 | include: "include", 72 | exclude: "exclude", 73 | dataset: "dataset", 74 | data_subset: "dataSubset", 75 | } 76 | }; -------------------------------------------------------------------------------- /src/util/buildClients.js: -------------------------------------------------------------------------------- 1 | const ClientBuilder = require("../ClientBuilder"); 2 | 3 | function instantiateClientBuilder(credentials) { 4 | return new ClientBuilder(credentials); 5 | } 6 | 7 | function buildUsStreetApiClient(credentials) { 8 | return instantiateClientBuilder(credentials).buildUsStreetApiClient(); 9 | } 10 | 11 | function buildUsAutocompleteProApiClient(credentials) { 12 | return instantiateClientBuilder(credentials).buildUsAutocompleteProClient(); 13 | } 14 | 15 | function buildUsExtractApiClient(credentials) { 16 | return instantiateClientBuilder(credentials).buildUsExtractClient(); 17 | } 18 | 19 | function buildUsZipcodeApiClient(credentials) { 20 | return instantiateClientBuilder(credentials).buildUsZipcodeClient(); 21 | } 22 | 23 | function buildInternationalStreetApiClient(credentials) { 24 | return instantiateClientBuilder(credentials).buildInternationalStreetClient(); 25 | } 26 | 27 | function buildUsReverseGeoApiClient(credentials) { 28 | return instantiateClientBuilder(credentials).buildUsReverseGeoClient(); 29 | } 30 | 31 | function buildInternationalAddressAutocompleteApiClient(credentials) { 32 | return instantiateClientBuilder(credentials).buildInternationalAddressAutocompleteClient(); 33 | } 34 | 35 | function buildUsEnrichmentApiClient(credentials) { 36 | return instantiateClientBuilder(credentials).buildUsEnrichmentClient(); 37 | } 38 | 39 | module.exports = { 40 | usStreet: buildUsStreetApiClient, 41 | usAutocompletePro: buildUsAutocompleteProApiClient, 42 | usExtract: buildUsExtractApiClient, 43 | usZipcode: buildUsZipcodeApiClient, 44 | internationalStreet: buildInternationalStreetApiClient, 45 | usReverseGeo: buildUsReverseGeoApiClient, 46 | internationalAddressAutocomplete: buildInternationalAddressAutocompleteApiClient, 47 | usEnrichment: buildUsEnrichmentApiClient, 48 | }; -------------------------------------------------------------------------------- /src/util/buildInputData.js: -------------------------------------------------------------------------------- 1 | const InputData = require("../InputData"); 2 | 3 | module.exports = (lookup, keyTranslationFormat) => { 4 | let inputData = new InputData(lookup); 5 | 6 | const hasCustomParameters = Object.keys(lookup.customParameters ?? {}).length > 0; 7 | 8 | for (let key in keyTranslationFormat) { 9 | inputData.add(key, keyTranslationFormat[key]); 10 | } 11 | 12 | if (hasCustomParameters) { 13 | for (let key in lookup.customParameters) { 14 | inputData.addCustomParameter(key, lookup.customParameters[key]); 15 | } 16 | } 17 | 18 | return inputData.data; 19 | }; 20 | -------------------------------------------------------------------------------- /src/util/buildSmartyResponse.js: -------------------------------------------------------------------------------- 1 | const Response = require("../Response.js"); 2 | 3 | function buildSmartyResponse(response, error) { 4 | if (response) return new Response(response.status, response.data, response.error, response.headers); 5 | return new Response(undefined, undefined, error) 6 | } 7 | 8 | module.exports = { 9 | buildSmartyResponse 10 | }; -------------------------------------------------------------------------------- /src/util/sendBatch.js: -------------------------------------------------------------------------------- 1 | const Request = require("../Request"); 2 | const Errors = require("../Errors"); 3 | const buildInputData = require("../util/buildInputData"); 4 | 5 | module.exports = (batch, sender, Result, keyTranslationFormat) => { 6 | if (batch.isEmpty()) throw new Errors.BatchEmptyError; 7 | 8 | let request = new Request(); 9 | 10 | if (batch.length() === 1) request.parameters = generateRequestPayload(batch)[0]; 11 | else request.payload = generateRequestPayload(batch); 12 | 13 | return new Promise((resolve, reject) => { 14 | sender.send(request) 15 | .then(response => { 16 | if (response.error) reject(response.error); 17 | 18 | resolve(assignResultsToLookups(batch, response)); 19 | }) 20 | .catch(reject); 21 | }); 22 | 23 | function generateRequestPayload(batch) { 24 | return batch.lookups.map((lookup) => { 25 | return buildInputData(lookup, keyTranslationFormat); 26 | }); 27 | } 28 | 29 | function assignResultsToLookups(batch, response) { 30 | response.payload.map(rawResult => { 31 | let result = new Result(rawResult); 32 | let lookup = batch.getByIndex(result.inputIndex); 33 | 34 | lookup.result.push(result); 35 | }); 36 | 37 | return batch; 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /tests/fixtures/MockSleeper.js: -------------------------------------------------------------------------------- 1 | class MockSleeper { 2 | constructor() { 3 | this.sleepDurations = []; 4 | } 5 | sleep(ms) { 6 | this.sleepDurations.push(ms); 7 | } 8 | } 9 | 10 | module.exports = MockSleeper; -------------------------------------------------------------------------------- /tests/fixtures/mock_senders.js: -------------------------------------------------------------------------------- 1 | const {buildSmartyResponse} = require("../../src/util/buildSmartyResponse.js"); 2 | const Response = require("../../src/Response"); 3 | 4 | module.exports = { 5 | MockSender: function () { 6 | let request = { 7 | payload: undefined, 8 | parameters: undefined, 9 | baseUrlParam: undefined, 10 | }; 11 | this.request = request; 12 | 13 | this.send = function (clientRequest) { 14 | request.payload = clientRequest.payload; 15 | request.parameters = clientRequest.parameters; 16 | request.baseUrlParam = clientRequest.baseUrlParam; 17 | } 18 | }, 19 | MockSenderWithResponse: function (expectedPayload, expectedError) { 20 | this.send = function () { 21 | return new Promise((resolve, reject) => { 22 | resolve(new Response("", expectedPayload, expectedError)); 23 | }); 24 | } 25 | }, 26 | MockSenderWithStatusCodesAndHeaders: function (statusCodes, headers = undefined, error = undefined) { 27 | this.statusCodes = statusCodes; 28 | this.headers = headers; 29 | this.error = error; 30 | this.currentStatusCodeIndex = 0; 31 | 32 | this.send = function (request) { 33 | let mockResponse = { 34 | status: this.statusCodes[this.currentStatusCodeIndex], 35 | headers: this.headers, 36 | error: this.error, 37 | }; 38 | const response = buildSmartyResponse(mockResponse); 39 | this.currentStatusCodeIndex += 1; 40 | return response; 41 | } 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /tests/international_address_autocomplete/test_Client.js: -------------------------------------------------------------------------------- 1 | const chai = require("chai"); 2 | const expect = chai.expect; 3 | const Client = require("../../src/international_address_autocomplete/Client"); 4 | const Lookup = require("../../src/international_address_autocomplete/Lookup"); 5 | const Suggestion = require("../../src/international_address_autocomplete/Suggestion"); 6 | const errors = require("../../src/Errors"); 7 | const MockSender = require("../fixtures/mock_senders").MockSender; 8 | const MockSenderWithResponse = require("../fixtures/mock_senders").MockSenderWithResponse; 9 | 10 | describe("An International Address Autocomplete Client", function () { 11 | it("correctly builds parameter", function () { 12 | let mockSender = new MockSender(); 13 | let client = new Client(mockSender); 14 | let search = "("; 15 | let lookup = new Lookup({search}); 16 | let expectedParameters = { 17 | max_results: 5, 18 | search: "(", 19 | }; 20 | 21 | client.send(lookup); 22 | expect(mockSender.request.parameters).to.deep.equal(expectedParameters); 23 | }); 24 | 25 | it("builds parameters for different country", function () { 26 | let mockSender = new MockSender(); 27 | let client = new Client(mockSender); 28 | let search = "("; 29 | let lookup = new Lookup({search}); 30 | lookup.search = search; 31 | lookup.country = "Russia"; 32 | let expectedParameters = { 33 | country: "Russia", 34 | max_results: 5, 35 | search: search, 36 | }; 37 | 38 | client.send(lookup); 39 | expect(mockSender.request.parameters).to.deep.equal(expectedParameters); 40 | }); 41 | 42 | it("builds parameters with different max results", function () { 43 | let mockSender = new MockSender(); 44 | let client = new Client(mockSender); 45 | let search = "("; 46 | let lookup = new Lookup({search}); 47 | lookup.search = search; 48 | lookup.maxResults = 10; 49 | let expectedParameters = { 50 | max_results: 10, 51 | search: search, 52 | }; 53 | 54 | client.send(lookup); 55 | expect(mockSender.request.parameters).to.deep.equal(expectedParameters); 56 | }); 57 | 58 | it("throws an error if sending without a lookup.", function () { 59 | let mockSender = new MockSender(); 60 | let client = new Client(mockSender); 61 | expect(client.send).to.throw(errors.UndefinedLookupError); 62 | }); 63 | 64 | it("attaches suggestions from a response to a lookup", function () { 65 | const responseData = { 66 | candidates: [ 67 | { 68 | "street": "L alleya", 69 | "locality": "Novosibirsk", 70 | "administrative_area": "Novosibirskaya oblast'", 71 | "postal_code": "40000", 72 | "country_iso3": "RUS", 73 | } 74 | ] 75 | }; 76 | 77 | let mockSender = new MockSenderWithResponse(responseData); 78 | let client = new Client(mockSender); 79 | let lookup = new Lookup({search: "f"}); 80 | let expectedSuggestion = new Suggestion(responseData.candidates[0]); 81 | 82 | return client.send(lookup).then(() => { 83 | expect(lookup.result[0]).to.deep.equal(expectedSuggestion); 84 | }); 85 | }); 86 | }); -------------------------------------------------------------------------------- /tests/international_address_autocomplete/test_Lookup.js: -------------------------------------------------------------------------------- 1 | const chai = require("chai"); 2 | const expect = chai.expect; 3 | const Lookup = require("../../src/international_address_autocomplete/Lookup"); 4 | 5 | describe("An International Address Autocomplete lookup", function () { 6 | it("Can be newed up with a prefix", function () { 7 | const expectedPrefix = "z"; 8 | let lookup = new Lookup({search: expectedPrefix}); 9 | expect(lookup.search).to.equal(expectedPrefix); 10 | }); 11 | 12 | it("Set address ID", function () { 13 | const addressId = "111"; 14 | let lookup = new Lookup({addressId}); 15 | expect(lookup.addressId).to.equal(addressId); 16 | }); 17 | 18 | it("Set country", function () { 19 | const country = "Russia"; 20 | let lookup = new Lookup({country}); 21 | expect(lookup.country).to.equal(country); 22 | }); 23 | 24 | it("Set max results", function () { 25 | const maxResults = 10000; 26 | let lookup = new Lookup({maxResults}); 27 | expect(lookup.maxResults).to.equal(maxResults); 28 | }); 29 | 30 | it("Set include only include locality param", function () { 31 | const onlyIncludeLocality = "locality"; 32 | let lookup = new Lookup({includeOnlyLocality: onlyIncludeLocality}); 33 | expect(lookup.includeOnlyLocality).to.equal(onlyIncludeLocality); 34 | }); 35 | 36 | it("Set include only include postal code param", function () { 37 | const onlyIncludePostalCode = "post code"; 38 | let lookup = new Lookup({includeOnlyPostalCode: onlyIncludePostalCode}); 39 | expect(lookup.includeOnlyPostalCode).to.equal(onlyIncludePostalCode); 40 | }); 41 | 42 | it("Checking defaults of params on instantiation ", function () { 43 | const defaultLookup = { 44 | result: [], 45 | search: undefined, 46 | addressId: undefined, 47 | country: undefined, 48 | maxResults: 5, 49 | includeOnlyLocality: undefined, 50 | includeOnlyPostalCode: undefined, 51 | customParameters: {}, 52 | }; 53 | let lookup = new Lookup(); 54 | expect(lookup).to.deep.equal(defaultLookup); 55 | }); 56 | }); -------------------------------------------------------------------------------- /tests/international_street/test_Candidate.js: -------------------------------------------------------------------------------- 1 | const chai = require("chai"); 2 | const expect = chai.expect; 3 | const Candidate = require("../../src/international_street/Candidate"); 4 | 5 | describe("An International match candidate", function () { 6 | it("populates with the appropriate fields.", function () { 7 | const sampleResponse = { 8 | organization: "1", 9 | address1: "2", 10 | address2: "3", 11 | address3: "4", 12 | address4: "5", 13 | address5: "6", 14 | address6: "7", 15 | address7: "8", 16 | address8: "9", 17 | address9: "10", 18 | address10: "11", 19 | address11: "12", 20 | address12: "13", 21 | components: { 22 | country_iso_3: "14", 23 | super_administrative_area: "15", 24 | administrative_area: "16", 25 | administrative_area_iso2: "16.1", 26 | administrative_area_short: "16.2", 27 | administrative_area_long: "16.3", 28 | sub_administrative_area: "17", 29 | dependent_locality: "18", 30 | dependent_locality_name: "19", 31 | double_dependent_locality: "20", 32 | locality: "21", 33 | postal_code: "22", 34 | postal_code_short: "23", 35 | postal_code_extra: "24", 36 | premise: "25", 37 | premise_extra: "26", 38 | premise_prefix_number: "26.5", 39 | premise_number: "27", 40 | premise_type: "28", 41 | thoroughfare: "29", 42 | thoroughfare_predirection: "30", 43 | thoroughfare_postdirection: "31", 44 | thoroughfare_name: "32", 45 | thoroughfare_trailing_type: "33", 46 | thoroughfare_type: "34", 47 | dependent_thoroughfare: "35", 48 | dependent_thoroughfare_predirection: "36", 49 | dependent_thoroughfare_postdirection: "37", 50 | dependent_thoroughfare_name: "38", 51 | dependent_thoroughfare_trailing_type: "39", 52 | dependent_thoroughfare_type: "40", 53 | building: "41", 54 | building_leading_type: "42", 55 | building_name: "43", 56 | building_trailing_type: "44", 57 | sub_building_type: "45", 58 | sub_building_number: "46", 59 | sub_building_name: "47", 60 | sub_building: "48", 61 | level_type: "48.1", 62 | level_number: "48.2", 63 | post_box: "49", 64 | post_box_type: "50", 65 | post_box_number: "51", 66 | }, 67 | metadata: { 68 | latitude: 52.0, 69 | longitude: 53.0, 70 | geocode_precision: "54", 71 | max_geocode_precision: "55", 72 | address_format: "56", 73 | }, 74 | analysis: { 75 | verification_status: "57", 76 | address_precision: "58", 77 | max_address_precision: "59", 78 | changes: { 79 | organization: "60", 80 | address1: "61", 81 | address2: "62", 82 | address3: "63", 83 | address4: "64", 84 | address5: "65", 85 | address6: "66", 86 | address7: "67", 87 | address8: "68", 88 | address9: "69", 89 | address10: "70", 90 | address11: "71", 91 | address12: "72", 92 | components: { 93 | country_iso_3: "73", 94 | super_administrative_area: "74", 95 | administrative_area: "75", 96 | administrative_area_short: "75.1", 97 | administrative_area_long: "75.2", 98 | sub_administrative_area: "76", 99 | dependent_locality: "77", 100 | dependent_locality_name: "78", 101 | double_dependent_locality: "79", 102 | locality: "80", 103 | postal_code: "81", 104 | postal_code_short: "82", 105 | postal_code_extra: "83", 106 | premise: "84", 107 | premise_extra: "85", 108 | premise_prefix_number: "86", 109 | premise_number: "87", 110 | premise_type: "88", 111 | thoroughfare: "89", 112 | thoroughfare_predirection: "90", 113 | thoroughfare_postdirection: "91", 114 | thoroughfare_name: "92", 115 | thoroughfare_trailing_type: "93", 116 | thoroughfare_type: "94", 117 | dependent_thoroughfare: "95", 118 | dependent_thoroughfare_predirection: "96", 119 | dependent_thoroughfare_postdirection: "97", 120 | dependent_thoroughfare_name: "98", 121 | dependent_thoroughfare_trailing_type: "99", 122 | dependent_thoroughfare_type: "100", 123 | building: "101", 124 | building_leading_type: "102", 125 | building_name: "103", 126 | building_trailing_type: "104", 127 | sub_building_type: "105", 128 | sub_building_number: "106", 129 | sub_building_name: "107", 130 | sub_building: "108", 131 | level_type: "108.1", 132 | level_number: "108.2", 133 | post_box: "109", 134 | post_box_type: "110", 135 | post_box_number: "111", 136 | }, 137 | }, 138 | }, 139 | }; 140 | 141 | const candidate = new Candidate(sampleResponse); 142 | 143 | expect(candidate.organization).to.equal("1"); 144 | expect(candidate.address1).to.equal("2"); 145 | expect(candidate.address2).to.equal("3"); 146 | expect(candidate.address3).to.equal("4"); 147 | expect(candidate.address4).to.equal("5"); 148 | expect(candidate.address5).to.equal("6"); 149 | expect(candidate.address6).to.equal("7"); 150 | expect(candidate.address7).to.equal("8"); 151 | expect(candidate.address8).to.equal("9"); 152 | expect(candidate.address9).to.equal("10"); 153 | expect(candidate.address10).to.equal("11"); 154 | expect(candidate.address11).to.equal("12"); 155 | expect(candidate.address12).to.equal("13"); 156 | let components = candidate.components; 157 | expect(components.countryIso3).to.equal("14"); 158 | expect(components.superAdministrativeArea).to.equal("15"); 159 | expect(components.administrativeArea).to.equal("16"); 160 | expect(components.administrativeAreaIso2).to.equal("16.1"); 161 | expect(components.administrativeAreaShort).to.equal("16.2"); 162 | expect(components.administrativeAreaLong).to.equal("16.3"); 163 | expect(components.subAdministrativeArea).to.equal("17"); 164 | expect(components.dependentLocality).to.equal("18"); 165 | expect(components.dependentLocalityName).to.equal("19"); 166 | expect(components.doubleDependentLocality).to.equal("20"); 167 | expect(components.locality).to.equal("21"); 168 | expect(components.postalCode).to.equal("22"); 169 | expect(components.postalCodeShort).to.equal("23"); 170 | expect(components.postalCodeExtra).to.equal("24"); 171 | expect(components.premise).to.equal("25"); 172 | expect(components.premiseExtra).to.equal("26"); 173 | expect(components.premisePrefixNumber).to.equal("26.5"); 174 | expect(components.premiseNumber).to.equal("27"); 175 | expect(components.premiseType).to.equal("28"); 176 | expect(components.thoroughfare).to.equal("29"); 177 | expect(components.thoroughfarePredirection).to.equal("30"); 178 | expect(components.thoroughfarePostdirection).to.equal("31"); 179 | expect(components.thoroughfareName).to.equal("32"); 180 | expect(components.thoroughfareTrailingType).to.equal("33"); 181 | expect(components.thoroughfareType).to.equal("34"); 182 | expect(components.dependentThoroughfare).to.equal("35"); 183 | expect(components.dependentThoroughfarePredirection).to.equal("36"); 184 | expect(components.dependentThoroughfarePostdirection).to.equal("37"); 185 | expect(components.dependentThoroughfareName).to.equal("38"); 186 | expect(components.dependentThoroughfareTrailingType).to.equal("39"); 187 | expect(components.dependentThoroughfareType).to.equal("40"); 188 | expect(components.building).to.equal("41"); 189 | expect(components.buildingLeadingType).to.equal("42"); 190 | expect(components.buildingName).to.equal("43"); 191 | expect(components.buildingTrailingType).to.equal("44"); 192 | expect(components.subBuildingType).to.equal("45"); 193 | expect(components.subBuildingNumber).to.equal("46"); 194 | expect(components.subBuildingName).to.equal("47"); 195 | expect(components.subBuilding).to.equal("48"); 196 | expect(components.levelType).to.equal("48.1"); 197 | expect(components.levelNumber).to.equal("48.2"); 198 | expect(components.postBox).to.equal("49"); 199 | expect(components.postBoxType).to.equal("50"); 200 | expect(components.postBoxNumber).to.equal("51"); 201 | let metadata = candidate.metadata; 202 | expect(metadata.latitude).to.equal(52.0); 203 | expect(metadata.longitude).to.equal(53.0); 204 | expect(metadata.geocodePrecision).to.equal("54"); 205 | expect(metadata.maxGeocodePrecision).to.equal("55"); 206 | expect(metadata.addressFormat).to.equal("56"); 207 | let analysis = candidate.analysis; 208 | expect(analysis.verificationStatus).to.equal("57"); 209 | expect(analysis.addressPrecision).to.equal("58"); 210 | expect(analysis.maxAddressPrecision).to.equal("59"); 211 | let changes = analysis.changes; 212 | expect(changes.organization).to.equal("60"); 213 | expect(changes.address1).to.equal("61"); 214 | expect(changes.address2).to.equal("62"); 215 | expect(changes.address3).to.equal("63"); 216 | expect(changes.address4).to.equal("64"); 217 | expect(changes.address5).to.equal("65"); 218 | expect(changes.address6).to.equal("66"); 219 | expect(changes.address7).to.equal("67"); 220 | expect(changes.address8).to.equal("68"); 221 | expect(changes.address9).to.equal("69"); 222 | expect(changes.address10).to.equal("70"); 223 | expect(changes.address11).to.equal("71"); 224 | expect(changes.address12).to.equal("72"); 225 | let ccomponents = changes.components; 226 | expect(ccomponents.countryIso3).to.equal("73"); 227 | expect(ccomponents.superAdministrativeArea).to.equal("74"); 228 | expect(ccomponents.administrativeArea).to.equal("75"); 229 | expect(ccomponents.administrativeAreaShort).to.equal("75.1"); 230 | expect(ccomponents.administrativeAreaLong).to.equal("75.2"); 231 | expect(ccomponents.subAdministrativeArea).to.equal("76"); 232 | expect(ccomponents.dependentLocality).to.equal("77"); 233 | expect(ccomponents.dependentLocalityName).to.equal("78"); 234 | expect(ccomponents.doubleDependentLocality).to.equal("79"); 235 | expect(ccomponents.locality).to.equal("80"); 236 | expect(ccomponents.postalCode).to.equal("81"); 237 | expect(ccomponents.postalCodeShort).to.equal("82"); 238 | expect(ccomponents.postalCodeExtra).to.equal("83"); 239 | expect(ccomponents.premise).to.equal("84"); 240 | expect(ccomponents.premiseExtra).to.equal("85"); 241 | expect(ccomponents.premisePrefixNumber).to.equal("86"); 242 | expect(ccomponents.premiseNumber).to.equal("87"); 243 | expect(ccomponents.premiseType).to.equal("88"); 244 | expect(ccomponents.thoroughfare).to.equal("89"); 245 | expect(ccomponents.thoroughfarePredirection).to.equal("90"); 246 | expect(ccomponents.thoroughfarePostdirection).to.equal("91"); 247 | expect(ccomponents.thoroughfareName).to.equal("92"); 248 | expect(ccomponents.thoroughfareTrailingType).to.equal("93"); 249 | expect(ccomponents.thoroughfareType).to.equal("94"); 250 | expect(ccomponents.dependentThoroughfare).to.equal("95"); 251 | expect(ccomponents.dependentThoroughfarePredirection).to.equal("96"); 252 | expect(ccomponents.dependentThoroughfarePostdirection).to.equal("97"); 253 | expect(ccomponents.dependentThoroughfareName).to.equal("98"); 254 | expect(ccomponents.dependentThoroughfareTrailingType).to.equal("99"); 255 | expect(ccomponents.dependentThoroughfareType).to.equal("100"); 256 | expect(ccomponents.building).to.equal("101"); 257 | expect(ccomponents.buildingLeadingType).to.equal("102"); 258 | expect(ccomponents.buildingName).to.equal("103"); 259 | expect(ccomponents.buildingTrailingType).to.equal("104"); 260 | expect(ccomponents.subBuildingType).to.equal("105"); 261 | expect(ccomponents.subBuildingNumber).to.equal("106"); 262 | expect(ccomponents.subBuildingName).to.equal("107"); 263 | expect(ccomponents.subBuilding).to.equal("108"); 264 | expect(ccomponents.levelType).to.equal("108.1"); 265 | expect(ccomponents.levelNumber).to.equal("108.2"); 266 | expect(ccomponents.postBox).to.equal("109"); 267 | expect(ccomponents.postBoxType).to.equal("110"); 268 | expect(ccomponents.postBoxNumber).to.equal("111"); 269 | }); 270 | }); 271 | -------------------------------------------------------------------------------- /tests/international_street/test_Client.js: -------------------------------------------------------------------------------- 1 | const chai = require("chai"); 2 | const expect = chai.expect; 3 | const Client = require("../../src/international_street/Client"); 4 | const Lookup = require("../../src/international_street/Lookup"); 5 | const Candidate = require("../../src/international_street/Candidate"); 6 | const errors = require("../../src/Errors"); 7 | const MockSender = require("../fixtures/mock_senders").MockSender; 8 | const MockSenderWithResponse = require("../fixtures/mock_senders").MockSenderWithResponse; 9 | 10 | describe("An International Street client", function () { 11 | it("has an inner sender.", function () { 12 | let mockSender = new MockSender(); 13 | let client = new Client(mockSender); 14 | 15 | expect(client.sender).to.deep.equal(mockSender); 16 | }); 17 | 18 | it("throws an error if sending without a lookup.", function () { 19 | let mockSender = new MockSender(); 20 | let client = new Client(mockSender); 21 | 22 | expect(client.send).to.throw(errors.UndefinedLookupError); 23 | }); 24 | 25 | it("correctly assigns request parameters based on lookup input.", function () { 26 | let mockSender = new MockSender(); 27 | let client = new Client(mockSender); 28 | let lookup = new Lookup("a", "b"); 29 | lookup.address1 = "c"; 30 | lookup.address2 = "d"; 31 | lookup.address3 = "e"; 32 | lookup.address4 = "f"; 33 | lookup.organization = "g"; 34 | lookup.locality = "h"; 35 | lookup.administrativeArea = "i"; 36 | lookup.postalCode = "j"; 37 | lookup.geocode = "k"; 38 | lookup.language = "l"; 39 | let expectedParameters = { 40 | country: "a", 41 | freeform: "b", 42 | address1: "c", 43 | address2: "d", 44 | address3: "e", 45 | address4: "f", 46 | organization: "g", 47 | locality: "h", 48 | administrative_area: "i", 49 | postal_code: "j", 50 | geocode: "k", 51 | language: "l", 52 | }; 53 | 54 | client.send(lookup); 55 | 56 | expect(mockSender.request.parameters).to.deep.equal(expectedParameters); 57 | }); 58 | 59 | it("attaches a match candidate from a response to a lookup.", function () { 60 | const expectedMockPayload = [{address1: "A", }]; 61 | let mockSender = new MockSenderWithResponse(expectedMockPayload); 62 | const client = new Client(mockSender); 63 | let lookup = new Lookup(); 64 | let expectedResult = new Candidate({address1: "A"}); 65 | 66 | return client.send(lookup).then(response => { 67 | expect(lookup.result[0]).to.deep.equal(expectedResult); 68 | }); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /tests/international_street/test_Lookup.js: -------------------------------------------------------------------------------- 1 | const chai = require("chai"); 2 | const expect = chai.expect; 3 | const Lookup = require("../../src/international_street/Lookup"); 4 | const errors = require("../../src/Errors"); 5 | 6 | describe("An International Street lookup", function () { 7 | const messages = { 8 | countryRequired: "Country field is required.", 9 | freeformOrAddress1Required: "Either freeform or address1 is required.", 10 | insufficientInformation: "Insufficient information: One or more required fields were not set on the lookup.", 11 | badGeocode: "Invalid input: geocode can only be set to 'true' (default is 'false'.", 12 | invalidLanguage: "Invalid input: language can only be set to 'latin' or 'native'. When not set, the the output language will match the language of the input values." 13 | }; 14 | 15 | it("correctly populates fields.", function () { 16 | let lookup = new Lookup("a", "b"); 17 | 18 | expect(lookup.country).to.equal("a"); 19 | expect(lookup.freeform).to.equal("b"); 20 | }); 21 | 22 | it("rejects lookups without a country.", function () { 23 | ensureValidationThrows(new Lookup().ensureEnoughInfo, messages.countryRequired); 24 | }); 25 | 26 | it("rejects lookups with only a country.", function () { 27 | ensureValidationThrows(new Lookup("a").ensureEnoughInfo, messages.freeformOrAddress1Required); 28 | }); 29 | 30 | it("rejects lookups with only a country and address 1.", function () { 31 | let lookup = new Lookup("a"); 32 | lookup.address1 = "b"; 33 | 34 | ensureValidationThrows(lookup.ensureEnoughInfo, messages.insufficientInformation); 35 | }); 36 | 37 | it("rejects lookups with only a country, address 1, and locality.", function () { 38 | let lookup = new Lookup("a"); 39 | lookup.address1 = "b"; 40 | lookup.locality = "c"; 41 | 42 | ensureValidationThrows(lookup.ensureEnoughInfo, messages.insufficientInformation); 43 | }); 44 | 45 | it("rejects lookups with only a country, address 1, and adminstrative area.", function () { 46 | let lookup = new Lookup("a"); 47 | lookup.address1 = "b"; 48 | lookup.administrativeArea = "c"; 49 | 50 | ensureValidationThrows(lookup.ensureEnoughInfo, messages.insufficientInformation); 51 | }); 52 | 53 | it("rejects lookups with an invalid geocode value.", function () { 54 | let lookup = new Lookup(); 55 | lookup.geocode = "Blarg!"; 56 | 57 | ensureValidationThrows(lookup.ensureValidData, messages.badGeocode); 58 | }); 59 | 60 | it("rejects lookups with an invalid language.", function () { 61 | let lookup = new Lookup(); 62 | lookup.language = "Rubberduckian"; 63 | 64 | ensureValidationThrows(lookup.ensureValidData, messages.invalidLanguage); 65 | }); 66 | 67 | it("accepts lookups with enough info.", function () { 68 | let lookup1 = new Lookup("a", "b"); 69 | 70 | let lookup2 = new Lookup("a"); 71 | lookup2.address1 = "b"; 72 | lookup2.postalCode = "c"; 73 | 74 | let lookup3 = new Lookup("a"); 75 | lookup3.address1 = "b"; 76 | lookup3.locality = "c"; 77 | lookup3.administrativeArea = "d"; 78 | 79 | expect(lookup1.ensureEnoughInfo()).to.equal(true); 80 | expect(lookup2.ensureEnoughInfo()).to.equal(true); 81 | expect(lookup3.ensureEnoughInfo()).to.equal(true); 82 | }); 83 | 84 | it("accepts lookups with a valid language.", function () { 85 | let lookup1 = new Lookup(); 86 | lookup1.language = "latin"; 87 | 88 | expect(lookup1.ensureValidData()).to.equal(true); 89 | 90 | let lookup2 = new Lookup(); 91 | lookup2.language = "native"; 92 | 93 | expect(lookup2.ensureValidData()).to.equal(true); 94 | }); 95 | 96 | function ensureValidationThrows(callback, message) { 97 | let expectedError = new errors.UnprocessableEntityError(message); 98 | 99 | try { 100 | callback(); 101 | expect(true).to.equal(false); 102 | } 103 | catch (error) { 104 | expect(error.message).to.equal(expectedError.message); 105 | expect(error).to.be.an.instanceOf(errors.UnprocessableEntityError); 106 | } 107 | } 108 | }); -------------------------------------------------------------------------------- /tests/test_AgentSender.js: -------------------------------------------------------------------------------- 1 | const chai = require("chai"); 2 | const expect = chai.expect; 3 | const Request = require("../src/Request"); 4 | const AgentSender = require("../src/AgentSender"); 5 | 6 | describe("An agent sender", function () { 7 | it ("attaches an 'agent' parameter to the request config.", function () { 8 | function MockSender() { 9 | this.agentString = ""; 10 | 11 | this.send = (request) => { 12 | this.agentString = request.parameters.agent; 13 | } 14 | } 15 | 16 | let mockSender = new MockSender(); 17 | let agentSender = new AgentSender(mockSender); 18 | let request = new Request(); 19 | let expectedAgentString = "smarty (sdk:javascript@" + require("../package.json").version + ")"; 20 | 21 | agentSender.send(request); 22 | 23 | expect(mockSender.agentString).to.equal(expectedAgentString); 24 | }); 25 | }); -------------------------------------------------------------------------------- /tests/test_BaseUrlSender.js: -------------------------------------------------------------------------------- 1 | const chai = require("chai"); 2 | const expect = chai.expect; 3 | const BaseUrlSender = require("../src/BaseUrlSender"); 4 | const Request = require("../src/Request"); 5 | 6 | describe("A base url sender", function () { 7 | let innerSender; 8 | let request; 9 | let urlOverride; 10 | let baseUrlSender; 11 | 12 | beforeEach(() => { 13 | innerSender = { 14 | send: () => true 15 | }; 16 | request = new Request(); 17 | urlOverride = "I'm in your base, killing your mans."; 18 | baseUrlSender = new BaseUrlSender(innerSender, urlOverride); 19 | }); 20 | 21 | it("replaces the request's base url with a new value.", function () { 22 | request.baseUrl = "All your baseUrl are belong to us."; 23 | baseUrlSender.send(request); 24 | 25 | expect(request.baseUrl).to.equal(urlOverride); 26 | }); 27 | 28 | it("returns a promise.", function () { 29 | expect(baseUrlSender.send(request) instanceof Promise).to.equal(true); 30 | }); 31 | }); -------------------------------------------------------------------------------- /tests/test_Batch.js: -------------------------------------------------------------------------------- 1 | const chai = require("chai"); 2 | const expect = chai.expect; 3 | const errors = require("../src/Errors"); 4 | const Batch = require("../src/Batch"); 5 | const Lookup = require("../src/us_street/Lookup"); 6 | 7 | describe("A batch", function () { 8 | let batch; 9 | 10 | beforeEach(function () { 11 | batch = new Batch(); 12 | }); 13 | 14 | it ("has a lookups field.", function () { 15 | expect(batch.hasOwnProperty("lookups")).to.equal(true); 16 | expect(Array.isArray(batch.lookups)).to.equal(true); 17 | }); 18 | 19 | it ("can add a lookup to its array of lookups.", function () { 20 | expect(batch.lookups.length).to.equal(0); 21 | batch.add("Hi."); 22 | expect(batch.lookups.length).to.equal(1); 23 | }); 24 | 25 | it ("errors if you add a lookup when it's full.", function () { 26 | for (let i = 0; i < 100; i++) { 27 | let lookup = {}; 28 | batch.add(lookup); 29 | } 30 | 31 | expect(() => batch.add({})).to.throw(errors.BatchFullError); 32 | }); 33 | 34 | it ("can be cleared.", function () { 35 | batch.add("Hi."); 36 | batch.clear(); 37 | expect(batch.lookups.length).to.equal(0); 38 | }); 39 | 40 | it ("has a length.", function () { 41 | expect(batch.length()).to.equal(0); 42 | for (let i = 0; i < 50; i++) { 43 | batch.add(i); 44 | } 45 | expect(batch.length()).to.equal(50); 46 | }); 47 | 48 | it ("returns a lookup by index.", function () { 49 | for (let i = 0; i < 100; i++) { 50 | batch.add(i); 51 | } 52 | 53 | expect(batch.getByIndex(50)).to.equal(50); 54 | }); 55 | 56 | it ("returns a lookup by input id.", function () { 57 | for (let i = 0; i < 100; i++) { 58 | let lookup = new Lookup(); 59 | lookup.inputId = i; 60 | batch.add(lookup); 61 | } 62 | 63 | let expectedLookup = batch.getByIndex(50); 64 | 65 | expect(batch.getByInputId(50)).to.deep.equal(expectedLookup); 66 | }); 67 | 68 | it ("knows if it's empty.", function () { 69 | expect(batch.isEmpty()).to.equal(true); 70 | batch.add("1"); 71 | expect(batch.isEmpty()).to.equal(false); 72 | }); 73 | }); -------------------------------------------------------------------------------- /tests/test_CustomHeaderSender.js: -------------------------------------------------------------------------------- 1 | const chai = require("chai"); 2 | const expect = chai.expect; 3 | const CustomHeaderSender = require("../src/CustomHeaderSender"); 4 | const Request = require("../src/Request"); 5 | 6 | describe("A custom header sender", function () { 7 | it ("adds custom headers to the request.", function () { 8 | function MockSender () { 9 | this.request; 10 | 11 | this.send = (request) => { 12 | this.request = request; 13 | } 14 | } 15 | 16 | let mockSender = new MockSender(); 17 | let customHeaders = { 18 | a: "1", 19 | b: "2", 20 | }; 21 | let customHeaderSender = new CustomHeaderSender(mockSender, customHeaders); 22 | let request = new Request(); 23 | 24 | customHeaderSender.send(request); 25 | 26 | expect(mockSender.request.headers.hasOwnProperty("a")).to.equal(true); 27 | expect(mockSender.request.headers.a).to.equal("1"); 28 | expect(mockSender.request.headers.hasOwnProperty("b")).to.equal(true); 29 | expect(mockSender.request.headers.b).to.equal("2"); 30 | }); 31 | }); -------------------------------------------------------------------------------- /tests/test_ExtractExample.mjs: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; // Importing expect directly from chai 2 | import SmartySDK from "../index.mjs"; // Importing SmartySDK 3 | 4 | describe("Extract example test", () => { 5 | const authId = ""; 6 | const authToken = ""; 7 | const SmartyCore = SmartySDK.core; 8 | const credentials = new SmartyCore.StaticCredentials(authId, authToken); 9 | const clientBuilder = new SmartyCore.ClientBuilder(credentials); 10 | clientBuilder.withBaseUrl("https://us-extract.api.hobbes.smartyops.net"); 11 | 12 | const usExtractClient = clientBuilder.buildUsExtractClient(); 13 | 14 | it("Check multi-line test", async () => { 15 | if (!(authId && authToken)) { 16 | expect("bypass").to.equal("bypass"); 17 | } else { 18 | const lookup = new SmartySDK.usExtract.Lookup(` 19 | 1476 Sandhill Rd, Orem, UT asdlfkjasldfkja sldfj 350 E University Pkwy, Orem, UT 84058 asldfkjasldfj asldkfjasldfj 20 | 417 W 1300 S, Orem, UT asdlfkjasldfkjal skdjf alskdjf 309 E University Pkwy, Orem, UT 84058 21 | `); 22 | 23 | lookup.addressesHaveLineBreaks = true; 24 | lookup.addressesPerLine = 1; 25 | 26 | const result = await usExtractClient.send(lookup); 27 | 28 | expect(result.result.meta.addressCount).to.equal(2); 29 | expect(result.result.meta.verifiedCount).to.equal(2); 30 | } 31 | }); 32 | 33 | it("Check HTML test", async () => { 34 | if (!(authId && authToken)) { 35 | expect("bypass").to.equal("bypass"); 36 | } else { 37 | const lookup = new SmartySDK.usExtract.Lookup(` 38 | HTML is automatically detected and, if found, ignored. You can override this behavior with API calls by manually setting the 'html' parameter to true or false. 39 | 40 | For input with HTML code like this, addresses will be scraped from outside the tags. The HTML should be properly formatted, valid HTML.

41 | 42 | 7584Big Canyon Anaheim Hills, CA 92808

43 | 44 | You can force HTML mode if auto-detect doesn't work properly. 45 | `); 46 | 47 | const result = await usExtractClient.send(lookup); 48 | 49 | expect(result.result.meta.addressCount).to.equal(1); 50 | } 51 | }); 52 | 53 | it("Check addresses with line breaks", async () => { 54 | if (!(authId && authToken)) { 55 | expect("bypass").to.equal("bypass"); 56 | } else { 57 | const lookup = new SmartySDK.usExtract.Lookup(` 58 | This address is valid: 59 | 60 | 1109 Ninth 85007 61 | 62 | but this one is not: 63 | 64 | 3777 Las Vegas Blvd 65 | Las Vegas, Nevada 66 | 67 | However, this nearby location is valid, despite the poor spelling: 68 | 69 | 3785 Las Vegs Av. 70 | Los Vegas, Nevada 71 | `); 72 | 73 | lookup.addressesHaveLineBreaks = true; 74 | 75 | const result = await usExtractClient.send(lookup); 76 | 77 | expect(result.result.meta.addressCount).to.equal(3); 78 | expect(result.result.meta.verifiedCount).to.equal(2); 79 | } 80 | }); 81 | 82 | it("Check addresses needing aggressive mode", async () => { 83 | if (!(authId && authToken)) { 84 | expect("bypass").to.equal("bypass"); 85 | } else { 86 | const lookup = new SmartySDK.usExtract.Lookup(` 87 | With aggressive mode on, popular US cities may be matched if no state or ZIP code can be found. 88 | 89 | This means addresses like 5455 North 250 West, Provo, can be found even though it doesn't have anything looking like a state or ZIP code following it. 90 | `); 91 | 92 | lookup.aggressive = true; 93 | 94 | const result = await usExtractClient.send(lookup); 95 | 96 | expect(result.result.meta.addressCount).to.equal(1); 97 | expect(result.result.meta.verifiedCount).to.equal(1); 98 | } 99 | }); 100 | 101 | it("Check addresses in URLs", async () => { 102 | if (!(authId && authToken)) { 103 | expect("bypass").to.equal("bypass"); 104 | } else { 105 | const lookup = new SmartySDK.usExtract.Lookup(` 106 | Smarty can handle addresses in URLs, as long as there are no spaces in them or they are surrounded by quotes. 107 | 108 | Address in one parameter: https://maps.google.com/?q=1+Rosedale+St+Baltimore+MD&ie=UTF8 109 | or, 110 | same URL with spaces surrounded by quotes: "https://maps.google.com/?q=1 Rosedale St Baltimore MD&ie=UTF8" 111 | ... 112 | 113 | Address across multiple parameters: https://example.com/?street=4004%20Grant%20St%20&state=WA&zipcode=98660&city=Vancouver 114 | We can find them either way. 115 | `); 116 | 117 | const result = await usExtractClient.send(lookup); 118 | 119 | expect(result.result.meta.addressCount).to.equal(3); 120 | expect(result.result.meta.verifiedCount).to.equal(3); 121 | } 122 | }); 123 | 124 | it("Check address with unicode character", async () => { 125 | if (!(authId && authToken)) { 126 | expect("bypass").to.equal("bypass"); 127 | } else { 128 | const lookup = new SmartySDK.usExtract.Lookup(` 129 | This address has a Unicode character in it (the special í in María): 123 María Lane Tempe, AZ, 85284 130 | 131 | Addresses with Unicode can still be found and verified. 132 | `); 133 | 134 | const result = await usExtractClient.send(lookup); 135 | 136 | expect(result.result.meta.addressCount).to.equal(1); 137 | expect(result.result.meta.verifiedCount).to.equal(1); 138 | } 139 | }); 140 | 141 | it("Check more addresses needing aggressive mode", async () => { 142 | if (!(authId && authToken)) { 143 | expect("bypass").to.equal("bypass"); 144 | } else { 145 | const lookup = new SmartySDK.usExtract.Lookup(` 146 | Try this one with and without aggressive mode. The '10379' looks like a ZIP code but is actually a primary number: 147 | 148 | 8465 Park Meadows Center Drive 149 | Lone Tree, CO 150 | 303-799-3400 151 | 152 | 10379 South State Street 153 | Sandy, UT 84070 154 | 801-432-5100 155 | 156 | This input has two addresses and only one is found without aggressive mode. 157 | `); 158 | 159 | const result = await usExtractClient.send(lookup); 160 | 161 | expect(result.result.meta.addressCount).to.equal(1); 162 | expect(result.result.meta.verifiedCount).to.equal(1); 163 | } 164 | }); 165 | }); 166 | -------------------------------------------------------------------------------- /tests/test_HttpSender.js: -------------------------------------------------------------------------------- 1 | const chai = require("chai"); 2 | const expect = chai.expect; 3 | const Request = require("../src/Request"); 4 | const HttpSender = require("../src/HttpSender"); 5 | const {buildSmartyResponse} = require("../src/util/buildSmartyResponse"); 6 | 7 | describe ("An Axios implementation of a HTTP sender", function () { 8 | it("adds a data payload to the HTTP request config.", function () { 9 | let expectedPayload = "test payload"; 10 | let request = new Request(expectedPayload); 11 | let sender = new HttpSender(); 12 | let requestConfig = sender.buildRequestConfig(request); 13 | 14 | expect(requestConfig.hasOwnProperty("data")).to.equal(true); 15 | expect(requestConfig.data).to.equal(expectedPayload); 16 | }); 17 | 18 | it ("adds a POST method to the HTTP request config when appropriate.", function () { 19 | let request = new Request("test payload"); 20 | let sender = new HttpSender(); 21 | let requestConfig = sender.buildRequestConfig(request); 22 | 23 | expect(requestConfig.hasOwnProperty("method")).to.equal(true); 24 | expect(requestConfig.method).to.equal("POST"); 25 | }); 26 | 27 | it ("adds a GET method to the HTTP request config when appropriate.", function () { 28 | let request = new Request(); 29 | let sender = new HttpSender(); 30 | let requestConfig = sender.buildRequestConfig(request); 31 | 32 | expect(requestConfig.hasOwnProperty("method")).to.equal(true); 33 | expect(requestConfig.method).to.equal("GET"); 34 | }); 35 | 36 | it ("add a timeout to the HTTP request config.", function () { 37 | let request = new Request("test payload"); 38 | let sender = new HttpSender(); 39 | let requestConfig = sender.buildRequestConfig(request); 40 | 41 | let customTimeoutSender = new HttpSender(5); 42 | let customTimeoutRequestConfig = customTimeoutSender.buildRequestConfig(request); 43 | 44 | expect(requestConfig.hasOwnProperty("timeout")).to.equal(true); 45 | expect(requestConfig.timeout).to.equal(10000); 46 | 47 | expect(customTimeoutRequestConfig.timeout).to.equal(5); 48 | }); 49 | 50 | it ("adds parameters to the HTTP request config.", function () { 51 | let request = new Request(""); 52 | let sender = new HttpSender(); 53 | request.parameters.test = "1"; 54 | let requestConfig = sender.buildRequestConfig(request); 55 | 56 | expect(requestConfig.hasOwnProperty("params")).to.equal(true); 57 | expect(requestConfig.params).to.deep.equal(request.parameters); 58 | }); 59 | 60 | it ("adds headers to the HTTP request config.", function () { 61 | let request = new Request(""); 62 | let sender = new HttpSender(); 63 | let requestConfig = sender.buildRequestConfig(request); 64 | let version = require("../package.json").version; 65 | 66 | expect(requestConfig.hasOwnProperty("headers")).to.equal(true); 67 | expect(requestConfig.headers["Content-Type"]).to.equal("application/json; charset=utf-8"); 68 | }); 69 | 70 | it ("has a response with the right status code.", function () { 71 | let sender = new HttpSender(); 72 | let mockResponse = { 73 | status: 200 74 | }; 75 | let smartyResponse = buildSmartyResponse(mockResponse); 76 | 77 | expect(smartyResponse.hasOwnProperty("statusCode")).to.equal(true); 78 | expect(smartyResponse.statusCode).to.equal(200); 79 | }); 80 | 81 | it ("has a response with a payload.", function () { 82 | let sender = new HttpSender(); 83 | let mockData = [1, 2, 3]; 84 | let mockResponse = { 85 | status: 200, 86 | data: mockData 87 | }; 88 | let smartyResponse = buildSmartyResponse(mockResponse); 89 | 90 | expect(smartyResponse.hasOwnProperty("payload")).to.equal(true); 91 | expect(smartyResponse.payload).to.equal(mockData); 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /tests/test_LicenseSender.js: -------------------------------------------------------------------------------- 1 | const chai = require("chai"); 2 | const expect = chai.expect; 3 | const LicenseSender = require("../src/LicenseSender"); 4 | const Request = require("../src/Request"); 5 | 6 | describe("A license sender", function () { 7 | let innerSender; 8 | let request; 9 | let licenses; 10 | let licenseSender; 11 | 12 | beforeEach(() => { 13 | innerSender = { 14 | send: () => true 15 | }; 16 | request = new Request(); 17 | }); 18 | 19 | it("appends licenses to parameters.", function () { 20 | licenses = ["0", "1", "2"]; 21 | licenseSender = new LicenseSender(innerSender, licenses); 22 | licenseSender.send(request); 23 | 24 | expect(request.parameters).contains({"license": "0,1,2"}); 25 | }); 26 | 27 | it("doesn't append license to query if array is empty.", function () { 28 | licenses = []; 29 | licenseSender = new LicenseSender(innerSender, licenses); 30 | licenseSender.send(request); 31 | 32 | expect(request.parameters).to.not.have.property("license"); 33 | }); 34 | 35 | it("returns a promise.", function () { 36 | licenses = []; 37 | licenseSender = new LicenseSender(innerSender, licenses); 38 | expect(licenseSender.send(request) instanceof Promise).to.equal(true); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /tests/test_RetrySender.js: -------------------------------------------------------------------------------- 1 | const chai = require("chai"); 2 | const expect = chai.expect; 3 | const RetrySender = require("../src/RetrySender"); 4 | const {MockSenderWithStatusCodesAndHeaders} = require("./fixtures/mock_senders"); 5 | const Request = require("../src/Request.js"); 6 | const MockSleeper = require("./fixtures/MockSleeper.js"); 7 | 8 | async function sendWithRetry(retries, inner, sleeper) { 9 | const request = new Request(); 10 | const sender = new RetrySender(retries, inner, sleeper); 11 | return await sender.send(request); 12 | } 13 | 14 | describe ("Retry Sender tests", function () { 15 | it("test success does not retry", async function () { 16 | let inner = new MockSenderWithStatusCodesAndHeaders(["200"]); 17 | await sendWithRetry(5, inner, new MockSleeper()); 18 | 19 | expect(inner.currentStatusCodeIndex).to.equal(1); 20 | }); 21 | 22 | it("test client error does not retry", async function () { 23 | let inner = new MockSenderWithStatusCodesAndHeaders(["422"]); 24 | await sendWithRetry(5, inner, new MockSleeper()); 25 | 26 | expect(inner.currentStatusCodeIndex).to.equal(1); 27 | }); 28 | 29 | it("test will retry until success", async function () { 30 | let inner = new MockSenderWithStatusCodesAndHeaders(["500", "500", "500", "200", "500"]); 31 | await sendWithRetry(10, inner, new MockSleeper()); 32 | 33 | expect(inner.currentStatusCodeIndex).to.equal(4); 34 | }); 35 | 36 | it("test return response if retry limit exceeded", async function () { 37 | let inner = new MockSenderWithStatusCodesAndHeaders(["500", "500", "500", "500", "500"]); 38 | const sleeper = new MockSleeper(); 39 | const response = await sendWithRetry(4, inner, sleeper); 40 | 41 | expect(response); 42 | expect(inner.currentStatusCodeIndex).to.equal(5); 43 | expect(response.statusCode).to.equal("500"); 44 | expect(sleeper.sleepDurations).to.deep.equal([0, 1, 2, 3]); 45 | }); 46 | 47 | it("test backoff does not exceed max", async function () { 48 | let inner = new MockSenderWithStatusCodesAndHeaders(["500", "500", "500", "500", "500", "500", "500", "500", "500", "500", "500", "500", "500", "200"]); 49 | const sleeper = new MockSleeper(); 50 | 51 | await sendWithRetry(20, inner, sleeper); 52 | 53 | expect(sleeper.sleepDurations).to.deep.equal([0,1,2,3,4,5,6,7,8,9,10,10,10]); 54 | }); 55 | 56 | it("test empty status does not retry", async function () { 57 | let inner = new MockSenderWithStatusCodesAndHeaders([]); 58 | await sendWithRetry(5, inner, new MockSleeper()); 59 | 60 | expect(inner.currentStatusCodeIndex).to.equal(1); 61 | }); 62 | 63 | it("test sleep on rate limit", async function () { 64 | let inner = new MockSenderWithStatusCodesAndHeaders(["429", "200"]); 65 | const sleeper = new MockSleeper(); 66 | 67 | await sendWithRetry(5, inner, sleeper); 68 | 69 | expect(sleeper.sleepDurations).to.deep.equal([10]); 70 | }); 71 | 72 | it("test rate limit error return", async function () { 73 | let inner = new MockSenderWithStatusCodesAndHeaders(["429"], {"Retry-After": 7}); 74 | const sleeper = new MockSleeper(); 75 | 76 | await sendWithRetry(10, inner, sleeper); 77 | 78 | expect(sleeper.sleepDurations).to.deep.equal([7]); 79 | }); 80 | 81 | it("test retry after invalid value", async function () { 82 | let inner = new MockSenderWithStatusCodesAndHeaders(["429"], {"Retry-After": "a"}); 83 | const sleeper = new MockSleeper(); 84 | 85 | await sendWithRetry(10, inner, sleeper); 86 | 87 | expect(sleeper.sleepDurations).to.deep.equal([10]); 88 | }); 89 | 90 | it("test retry error", async function () { 91 | let inner = new MockSenderWithStatusCodesAndHeaders(["429"], undefined, "Big Bad"); 92 | const sleeper = new MockSleeper(); 93 | 94 | const response = await sendWithRetry(10, inner, sleeper); 95 | 96 | expect(response.error).to.equal("Big Bad"); 97 | }); 98 | }); -------------------------------------------------------------------------------- /tests/test_SigningSender.js: -------------------------------------------------------------------------------- 1 | const chai = require("chai"); 2 | const expect = chai.expect; 3 | const Request = require("../src/Request"); 4 | const SigningSender = require("../src/SigningSender"); 5 | const StaticCredentials = require("../src/StaticCredentials"); 6 | const SharedCredentials = require("../src/SharedCredentials"); 7 | const Errors = require("../src/Errors"); 8 | 9 | describe("A signing sender", function () { 10 | let mockAuthId = "testId"; 11 | let mockAuthToken = "testToken"; 12 | let mockHostName = "testHostName"; 13 | let mockSender = { 14 | send: request => new Promise() 15 | }; 16 | let request; 17 | 18 | beforeEach(function () { 19 | request = new Request(); 20 | }); 21 | 22 | it("signs a request with static credentials.", function () { 23 | let staticCredentials = new StaticCredentials(mockAuthId, mockAuthToken); 24 | let signingSender = new SigningSender(mockSender, staticCredentials); 25 | 26 | signingSender.send(request); 27 | 28 | expect(request.parameters.hasOwnProperty("auth-id")).to.equal(true); 29 | expect(request.parameters["auth-id"]).to.equal(mockAuthId); 30 | expect(request.parameters.hasOwnProperty("auth-token")).to.equal(true); 31 | expect(request.parameters["auth-token"]).to.equal(mockAuthToken); 32 | }); 33 | 34 | it("signs a request with shared credentials.", function () { 35 | let sharedCredentials = new SharedCredentials(mockAuthId, mockHostName); 36 | let signingSender = new SigningSender(mockSender, sharedCredentials); 37 | 38 | signingSender.send(request); 39 | 40 | expect(request.parameters.hasOwnProperty("key")).to.equal(true); 41 | expect(request.parameters["key"]).to.equal(mockAuthId); 42 | expect(request.headers.hasOwnProperty("Referer")).to.equal(true); 43 | expect(request.headers["Referer"]).to.equal("https://" + mockHostName); 44 | }); 45 | 46 | it("errors if signing a POST request with shared credentials.", function () { 47 | let sharedCredentials = new SharedCredentials(mockAuthId, mockHostName); 48 | let signingSender = new SigningSender(mockSender, sharedCredentials); 49 | let mockRequestPayload = { 50 | foo: "bar", 51 | }; 52 | request.payload = mockRequestPayload; 53 | 54 | expect(() => signingSender.send(request)).to.throw(Errors.UnprocessableEntityError); 55 | }); 56 | }); -------------------------------------------------------------------------------- /tests/test_StatusCodeSender.js: -------------------------------------------------------------------------------- 1 | const chai = require("chai"); 2 | const expect = chai.expect; 3 | const StatusCodeSender = require("../src/StatusCodeSender"); 4 | const Response = require("../src/Response"); 5 | const Request = require("../src/Request"); 6 | const errors = require("../src/Errors"); 7 | 8 | describe("A status code sender", function () { 9 | it("doesn't attach an error on a 200.", function () { 10 | let mockSender = { 11 | send: () => { 12 | return new Promise((resolve, reject) => { 13 | resolve(new Response(200)); 14 | }); 15 | } 16 | }; 17 | 18 | let statusCodeSender = new StatusCodeSender(mockSender); 19 | let request = new Request(); 20 | 21 | return statusCodeSender.send(request).then(response => { 22 | expect(response.error === undefined).to.equal(true); 23 | }); 24 | }); 25 | 26 | it("gives a custom message for 400", function () { 27 | const payload = { 28 | errors: [ 29 | {message: "custom message"} 30 | ] 31 | }; 32 | return expectedErrorWithPayloadMessage(400, payload); 33 | }) 34 | 35 | it("returns an error message if payload is undefined", function () { 36 | return expectedDefaultError() 37 | }) 38 | 39 | it("gives an Internal Server Error on a 500.", function () { 40 | return expectedErrorForStatusCode(errors.InternalServerError, 500); 41 | }); 42 | 43 | it("gives an Service Unavailable error on a 503.", function () { 44 | return expectedErrorForStatusCode(errors.ServiceUnavailableError, 503); 45 | }); 46 | 47 | it("gives an Gateway Timeout error on a 504.", function () { 48 | return expectedErrorForStatusCode(errors.GatewayTimeoutError, 504); 49 | }); 50 | }); 51 | 52 | const expectedErrorWithPayloadMessage = (errorCode, payload) => { 53 | let mockSender = generateMockSender(errorCode, payload); 54 | let statusCodeSender = new StatusCodeSender(mockSender); 55 | let request = new Request(); 56 | 57 | return statusCodeSender.send(request).then(() => { 58 | }, error => { 59 | expect(error.error).to.be.an.instanceOf(errors.DefaultError); 60 | expect(error.error.message).to.be.equal(payload.errors[0].message); 61 | }) 62 | } 63 | 64 | const expectedDefaultError = () => { 65 | let mockSender = generateMockSender(400); 66 | let statusCodeSender = new StatusCodeSender(mockSender); 67 | let request = new Request(); 68 | 69 | return statusCodeSender.send(request).then(() => { 70 | }, error => { 71 | expect(error.error).to.be.an.instanceOf(errors.DefaultError); 72 | expect(error.error.message).to.be.equal("unexpected error"); 73 | }) 74 | } 75 | 76 | function expectedErrorForStatusCode(expectedError, errorCode) { 77 | let mockSender = generateMockSender(errorCode); 78 | let statusCodeSender = new StatusCodeSender(mockSender); 79 | let request = new Request(); 80 | 81 | return statusCodeSender.send(request).then(() => { 82 | }, error => { 83 | expect(error.error).to.be.an.instanceOf(expectedError); 84 | }) 85 | } 86 | 87 | function generateMockSender(errorCode, payload) { 88 | return { 89 | send: () => { 90 | return new Promise((resolve, reject) => { 91 | reject(new Response(errorCode, payload)) 92 | }); 93 | } 94 | }; 95 | } 96 | -------------------------------------------------------------------------------- /tests/us_autocomplete_pro/test_Client.js: -------------------------------------------------------------------------------- 1 | const chai = require("chai"); 2 | const expect = chai.expect; 3 | const Client = require("../../src/us_autocomplete_pro/Client"); 4 | const Lookup = require("../../src/us_autocomplete_pro/Lookup"); 5 | const Suggestion = require("../../src/us_autocomplete_pro/Suggestion"); 6 | const errors = require("../../src/Errors"); 7 | const MockSender = require("../fixtures/mock_senders").MockSender; 8 | const MockSenderWithResponse = require("../fixtures/mock_senders").MockSenderWithResponse; 9 | 10 | describe("A US Autocomplete Pro Client", function () { 11 | it("correctly builds parameters for a prefix only lookup.", function () { 12 | let mockSender = new MockSender(); 13 | let client = new Client(mockSender); 14 | let search = '(>")>#'; 15 | let lookup = new Lookup(search); 16 | let expectedParameters = { 17 | exclude_states: "", 18 | include_only_cities: "", 19 | include_only_states: "", 20 | include_only_zip_codes: "", 21 | prefer_cities: "", 22 | prefer_states: "", 23 | prefer_zip_codes: "", 24 | search: search, 25 | }; 26 | 27 | client.send(lookup); 28 | 29 | expect(mockSender.request.parameters).to.deep.equal(expectedParameters); 30 | }); 31 | 32 | it("correctly builds parameters for a fully-populated lookup.", function () { 33 | let mockSender = new MockSender(); 34 | let client = new Client(mockSender); 35 | let lookup = new Lookup(); 36 | lookup.search = "1"; 37 | lookup.selected = "2"; 38 | lookup.maxResults = "3"; 39 | lookup.includeOnlyCities = ["a,b", "c,d"]; 40 | lookup.includeOnlyStates = ["e", "f"]; 41 | lookup.includeOnlyZIPCodes = ["g", "h"]; 42 | lookup.excludeStates = ["i", "j"]; 43 | lookup.preferCities = ["k,l", "m,n"]; 44 | lookup.preferStates = ["o", "p"]; 45 | lookup.preferZIPCodes = ["q", "r"]; 46 | lookup.preferRatio = "s"; 47 | lookup.preferGeolocation = "t"; 48 | lookup.source = "all"; 49 | 50 | let expectedParameters = { 51 | search: "1", 52 | selected: "2", 53 | max_results: "3", 54 | include_only_cities: "a,b;c,d", 55 | include_only_states: "e;f", 56 | include_only_zip_codes: "g;h", 57 | exclude_states: "i;j", 58 | prefer_cities: "k,l;m,n", 59 | prefer_states: "o;p", 60 | prefer_zip_codes: "q;r", 61 | prefer_ratio: "s", 62 | prefer_geolocation: "t", 63 | source: "all", 64 | }; 65 | 66 | client.send(lookup); 67 | expect(mockSender.request.parameters).to.deep.equal(expectedParameters); 68 | }); 69 | 70 | it("throws an error if sending without a lookup.", function () { 71 | let mockSender = new MockSender(); 72 | let client = new Client(mockSender); 73 | expect(client.send).to.throw(errors.UndefinedLookupError); 74 | }); 75 | 76 | it("rejects with an exception if the response comes back with an error.", function () { 77 | let expectedError = new Error("I'm the error."); 78 | let mockSender = new MockSenderWithResponse("", expectedError); 79 | let client = new Client(mockSender); 80 | let lookup = new Lookup("¯\\_(ツ)_/¯"); 81 | 82 | return client.send(lookup).catch((e) => {expect(e).to.equal(expectedError);}); 83 | }); 84 | 85 | it("returns an empty array when no suggestions are returned.", () => { 86 | let mockExpectedPayload = {suggestions: null}; 87 | let mockSender = new MockSenderWithResponse(mockExpectedPayload); 88 | let client = new Client(mockSender); 89 | let lookup = new Lookup("Please let this be easy to test."); 90 | let expectedSuggestion = []; 91 | 92 | return client.send(lookup).then(response => { 93 | expect(lookup.result).to.deep.equal(expectedSuggestion); 94 | }); 95 | }); 96 | 97 | it("attaches suggestions from a response to a lookup.", function () { 98 | const responseData = { 99 | streetLine: "a", 100 | secondary: "b", 101 | city: "c", 102 | state: "d", 103 | zipcode: "e", 104 | entries: "f", 105 | }; 106 | let mockExpectedPayload = {suggestions: [responseData]}; 107 | let mockSender = new MockSenderWithResponse(mockExpectedPayload); 108 | let client = new Client(mockSender); 109 | let lookup = new Lookup("Trevor the Vampire"); 110 | let expectedSuggestion = new Suggestion(responseData); 111 | 112 | return client.send(lookup).then(response => { 113 | expect(lookup.result[0]).to.deep.equal(expectedSuggestion); 114 | }); 115 | }) 116 | }); 117 | -------------------------------------------------------------------------------- /tests/us_autocomplete_pro/test_Lookup.js: -------------------------------------------------------------------------------- 1 | const chai = require("chai"); 2 | const expect = chai.expect; 3 | const Lookup = require("../../src/us_autocomplete_pro/Lookup"); 4 | 5 | describe("A US Autocomplete Pro Lookup", function () { 6 | it("can be newed up with a prefix.", function () { 7 | const expectedSearch = "a"; 8 | let lookup = new Lookup(expectedSearch); 9 | expect(lookup.search).to.equal(expectedSearch); 10 | }); 11 | }); -------------------------------------------------------------------------------- /tests/us_autocomplete_pro/test_Suggestion.js: -------------------------------------------------------------------------------- 1 | const chai = require("chai"); 2 | const expect = chai.expect; 3 | const Suggestion = require("../../src/us_autocomplete_pro/Suggestion"); 4 | 5 | describe("A US Autocomplete Pro Suggestion", function () { 6 | it("is initialized correctly with API response data.", function () { 7 | const mockSuggestion = { 8 | street_line: "a", 9 | secondary: "b", 10 | city: "c", 11 | state: "d", 12 | zipcode: "e", 13 | entries: "f", 14 | }; 15 | let suggestion = new Suggestion(mockSuggestion); 16 | 17 | expect(suggestion.streetLine).to.equal("a"); 18 | expect(suggestion.secondary).to.equal("b"); 19 | expect(suggestion.city).to.equal("c"); 20 | expect(suggestion.state).to.equal("d"); 21 | expect(suggestion.zipcode).to.equal("e"); 22 | expect(suggestion.entries).to.equal("f"); 23 | }); 24 | }); -------------------------------------------------------------------------------- /tests/us_enrichment/test_Lookup.js: -------------------------------------------------------------------------------- 1 | const chai = require("chai"); 2 | const expect = chai.expect; 3 | const Lookup = require("../../src/us_enrichment/Lookup"); 4 | 5 | describe("A US Enrichment Lookup", function () { 6 | it("can be newed up with all basic fields.", function () { 7 | const expectedSmartyKey = "a"; 8 | const expectedInclude = "b"; 9 | const expectedExclude = "c"; 10 | const expectedDataset = "d"; 11 | const expectedDataSubset = "e"; 12 | 13 | let lookup = new Lookup(expectedSmartyKey, expectedInclude, expectedExclude, expectedDataset, expectedDataSubset); 14 | expect(lookup.smartyKey).to.equal(expectedSmartyKey); 15 | expect(lookup.include).to.equal(expectedInclude); 16 | expect(lookup.exclude).to.equal(expectedExclude); 17 | expect(lookup.dataset).to.equal(expectedDataset); 18 | expect(lookup.dataSubset).to.equal(expectedDataSubset); 19 | }); 20 | 21 | 22 | }); -------------------------------------------------------------------------------- /tests/us_extract/test_Address.js: -------------------------------------------------------------------------------- 1 | const chai = require("chai"); 2 | const expect = chai.expect; 3 | const Address = require("../../src/us_extract/Address"); 4 | const Candidate = require("../../src/us_street/Candidate"); 5 | 6 | describe("A US Extract Address", function () { 7 | it("populates fields correctly.", function () { 8 | let mockResponseAddress = { 9 | "text": "5732 Lincoln Drive Minneapolis MN", 10 | "verified": true, 11 | "line": 4, 12 | "start": 16, 13 | "end": 49, 14 | "api_output": [ 15 | { 16 | "candidate_index": 0, 17 | "delivery_line_1": "5732 Lincoln Dr", 18 | "last_line": "Minneapolis MN 55436-1608", 19 | "delivery_point_barcode": "554361608327", 20 | "components": { 21 | "primary_number": "5732", 22 | "street_name": "Lincoln", 23 | "street_suffix": "Dr", 24 | "city_name": "Minneapolis", 25 | "state_abbreviation": "MN", 26 | "zipcode": "55436", 27 | "plus4_code": "1608", 28 | "delivery_point": "32", 29 | "delivery_point_check_digit": "7" 30 | }, 31 | "metadata": { 32 | "record_type": "S", 33 | "zip_type": "Standard", 34 | "county_fips": "27053", 35 | "county_name": "Hennepin", 36 | "carrier_route": "C009", 37 | "congressional_district": "03", 38 | "rdi": "Commercial", 39 | "elot_sequence": "0035", 40 | "elot_sort": "A", 41 | "latitude": 44.90127, 42 | "longitude": -93.40045, 43 | "precision": "Zip9", 44 | "time_zone": "Central", 45 | "utc_offset": -6, 46 | "dst": true 47 | }, 48 | "analysis": { 49 | "dpv_match_code": "Y", 50 | "dpv_footnotes": "AABB", 51 | "dpv_cmra": "N", 52 | "dpv_vacant": "N", 53 | "active": "Y", 54 | "footnotes": "N#" 55 | }, 56 | }, 57 | ], 58 | }; 59 | let address = new Address(mockResponseAddress); 60 | expect(address.candidates[0]).to.be.an.instanceOf(Candidate); 61 | expect(address.text).to.equal(mockResponseAddress.text); 62 | expect(address.verified).to.equal(mockResponseAddress.verified); 63 | expect(address.line).to.equal(mockResponseAddress.line); 64 | expect(address.start).to.equal(mockResponseAddress.start); 65 | expect(address.end).to.equal(mockResponseAddress.end); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /tests/us_extract/test_Client.js: -------------------------------------------------------------------------------- 1 | const chai = require("chai"); 2 | const expect = chai.expect; 3 | const Client = require("../../src/us_extract/Client"); 4 | const Lookup = require("../../src/us_extract/Lookup"); 5 | const Result = require("../../src/us_extract/Result"); 6 | const MockSender = require("../fixtures/mock_senders").MockSender; 7 | const MockSenderWithResponse = require("../fixtures/mock_senders").MockSenderWithResponse; 8 | const errors = require("../../src/Errors"); 9 | 10 | describe("A US Extract Client", function () { 11 | it("throws an error if sending without a lookup.", function () { 12 | let mockSender = new MockSender(); 13 | let client = new Client(mockSender); 14 | 15 | expect(client.send).to.throw(errors.UndefinedLookupError); 16 | }); 17 | 18 | it("correctly builds a payload for a text only lookup.", function () { 19 | let mockSender = new MockSender(); 20 | let client = new Client(mockSender); 21 | let mockText = "yump."; 22 | let lookup = new Lookup(mockText); 23 | let expectedPayload = mockText; 24 | 25 | client.send(lookup); 26 | 27 | expect(mockSender.request.payload).to.deep.equal(expectedPayload); 28 | }); 29 | 30 | it("correctly builds a payload for a fully-populated lookup.", function () { 31 | let mockSender = new MockSender(); 32 | let client = new Client(mockSender); 33 | const mockText = "The flocculated scunge is subprime for human consumption."; 34 | let lookup = new Lookup(mockText); 35 | let expectedPayload = mockText; 36 | 37 | client.send(lookup); 38 | 39 | expect(mockSender.request.payload).to.deep.equal(expectedPayload); 40 | }); 41 | 42 | it("correctly builds parameters for a lookup.", () => { 43 | let mockSender = new MockSender(); 44 | let client = new Client(mockSender); 45 | const mockText = "Picard is coming back. All power to the engines."; 46 | let lookup = new Lookup(mockText); 47 | lookup.html = 1; 48 | lookup.aggressive = 2; 49 | lookup.addressesHaveLineBreaks = 3; 50 | lookup.addressesPerLine = 4; 51 | 52 | let expectedParams = { 53 | html: 1, 54 | aggressive: 2, 55 | addr_line_breaks: 3, 56 | addr_per_line: 4, 57 | }; 58 | 59 | client.send(lookup); 60 | 61 | expect(mockSender.request.parameters).to.deep.equal(expectedParams); 62 | }); 63 | 64 | it("rejects with an exception if the response comes back with an error.", function () { 65 | let expectedError = new Error("I'm the error."); 66 | let mockSender = new MockSenderWithResponse("", expectedError); 67 | let client = new Client(mockSender); 68 | let lookup = new Lookup("Shine on you crazy diamond."); 69 | 70 | return client.send(lookup).catch((e) => {expect(e).to.equal(expectedError);}); 71 | }); 72 | 73 | it("attaches result from a response to a lookup.", function () { 74 | const responseData = { 75 | "meta": { 76 | "lines": 6, 77 | "unicode": false, 78 | "address_count": 1, 79 | "verified_count": 1, 80 | "bytes": 53, 81 | "character_count": 53 82 | }, 83 | "addresses": [ 84 | { 85 | "text": "5732 Lincoln Drive Minneapolis MN", 86 | "verified": true, 87 | "line": 4, 88 | "start": 16, 89 | "end": 49, 90 | "api_output": [ 91 | { 92 | "candidate_index": 0, 93 | "delivery_line_1": "5732 Lincoln Dr", 94 | "last_line": "Minneapolis MN 55436-1608", 95 | "delivery_point_barcode": "554361608327", 96 | "components": { 97 | "primary_number": "5732", 98 | "street_name": "Lincoln", 99 | "street_suffix": "Dr", 100 | "city_name": "Minneapolis", 101 | "state_abbreviation": "MN", 102 | "zipcode": "55436", 103 | "plus4_code": "1608", 104 | "delivery_point": "32", 105 | "delivery_point_check_digit": "7" 106 | }, 107 | "metadata": { 108 | "record_type": "S", 109 | "zip_type": "Standard", 110 | "county_fips": "27053", 111 | "county_name": "Hennepin", 112 | "carrier_route": "C009", 113 | "congressional_district": "03", 114 | "rdi": "Commercial", 115 | "elot_sequence": "0035", 116 | "elot_sort": "A", 117 | "latitude": 44.90127, 118 | "longitude": -93.40045, 119 | "precision": "Zip9", 120 | "time_zone": "Central", 121 | "utc_offset": -6, 122 | "dst": true 123 | }, 124 | "analysis": { 125 | "dpv_match_code": "Y", 126 | "dpv_footnotes": "AABB", 127 | "dpv_cmra": "N", 128 | "dpv_vacant": "N", 129 | "dpv_no_stat": "N", 130 | "active": "Y", 131 | "footnotes": "N#" 132 | } 133 | } 134 | ] 135 | } 136 | ] 137 | }; 138 | 139 | let mockSender = new MockSenderWithResponse(responseData); 140 | let client = new Client(mockSender); 141 | let lookup = new Lookup("Sometimes when you're testing this field doesn't matter too much."); 142 | let expectedResult = new Result(responseData); 143 | 144 | return client.send(lookup).then(response => { 145 | expect(lookup.result).to.deep.equal(expectedResult); 146 | }); 147 | }) 148 | }); -------------------------------------------------------------------------------- /tests/us_extract/test_Lookup.js: -------------------------------------------------------------------------------- 1 | const chai = require("chai"); 2 | const expect = chai.expect; 3 | const Lookup = require("../../src/us_extract/Lookup"); 4 | 5 | describe("A US Extract Lookup", function () { 6 | it("correctly populates fields.", function () { 7 | let lookup = new Lookup("some text"); 8 | lookup.html = true; 9 | lookup.aggressive = true; 10 | lookup.addressesHaveLineBreaks = true; 11 | lookup.addressesPerLine = 10; 12 | 13 | expect(lookup.text).to.equal("some text"); 14 | expect(lookup.html).to.equal(true); 15 | expect(lookup.aggressive).to.equal(true); 16 | expect(lookup.addressesHaveLineBreaks).to.equal(true); 17 | expect(lookup.addressesPerLine).to.equal(10); 18 | }); 19 | }); -------------------------------------------------------------------------------- /tests/us_extract/test_Result.js: -------------------------------------------------------------------------------- 1 | const chai = require("chai"); 2 | const expect = chai.expect; 3 | const Result = require("../../src/us_extract/Result"); 4 | const Address = require("../../src/us_extract/Address"); 5 | 6 | describe("A US Extract Result", function () { 7 | it("correctly populates fields.", function () { 8 | const mockResponseData = { 9 | "meta": { 10 | "lines": 6, 11 | "unicode": false, 12 | "address_count": 1, 13 | "verified_count": 1, 14 | "bytes": 53, 15 | "character_count": 53, 16 | }, 17 | "addresses": [ 18 | { 19 | "text": "5732 Lincoln Drive Minneapolis MN", 20 | "verified": true, 21 | "line": 4, 22 | "start": 16, 23 | "end": 49, 24 | "api_output": [{}] 25 | } 26 | ] 27 | }; 28 | let result = new Result(mockResponseData); 29 | 30 | expect(result.meta.lines).to.equal(6); 31 | expect(result.meta.unicode).to.equal(false); 32 | expect(result.meta.addressCount).to.equal(1); 33 | expect(result.meta.verifiedCount).to.equal(1); 34 | expect(result.meta.bytes).to.equal(53); 35 | expect(result.meta.characterCount).to.equal(53); 36 | 37 | expect(result.addresses[0]).to.be.an.instanceOf(Address); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /tests/us_reverse_geo/test_Client.js: -------------------------------------------------------------------------------- 1 | const chai = require("chai"); 2 | const expect = chai.expect; 3 | const Client = require("../../src/us_reverse_geo/Client"); 4 | const Lookup = require("../../src/us_reverse_geo/Lookup"); 5 | const MockSender = require("../fixtures/mock_senders").MockSender; 6 | const MockSenderWithResponse = require("../fixtures/mock_senders").MockSenderWithResponse; 7 | const Response = require("../../src/us_reverse_geo/Response"); 8 | 9 | describe("A US Reverse Geo client", function () { 10 | it("has an inner sender.", function () { 11 | let mockSender = new MockSender(); 12 | let client = new Client(mockSender); 13 | 14 | expect(client.sender).to.deep.equal(mockSender); 15 | }); 16 | 17 | 18 | it("attaches a result from a response to a lookup.", function () { 19 | const expectedMockPayload = { 20 | "results": [ 21 | { 22 | "coordinate": { 23 | "latitude": 40.111111, 24 | "longitude": -111.111111, 25 | "accuracy": "Rooftop", 26 | "license": "SmartyStreets" 27 | }, 28 | "distance": 2.7207432, 29 | "address": { 30 | "street": "2335 S State St", 31 | "city": "Provo", 32 | "state_abbreviation": "UT", 33 | "zipcode": "84606", 34 | "source": "postal" 35 | } 36 | }, 37 | ] 38 | }; 39 | let mockSender = new MockSenderWithResponse(expectedMockPayload); 40 | const client = new Client(mockSender); 41 | let lookup = new Lookup(44.888888888, -111.111111111, "postal"); 42 | 43 | return client.send(lookup).then(() => { 44 | expect(lookup.response).to.deep.equal(expectedMockPayload); 45 | expect(lookup.response).to.be.an.instanceOf(Response); 46 | }); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /tests/us_reverse_geo/test_Lookup.js: -------------------------------------------------------------------------------- 1 | const chai = require("chai"); 2 | const expect = chai.expect; 3 | const Lookup = require("../../src/us_reverse_geo/Lookup"); 4 | 5 | describe("A US Reverse Geo lookup", function () { 6 | it("correctly populates fields.", function () { 7 | let lookup = new Lookup(44.888888888, -111.111111111, "postal"); 8 | 9 | expect(lookup.source).to.equal("postal") 10 | expect(lookup.latitude).to.equal("44.88888889"); 11 | expect(lookup.longitude).to.equal("-111.11111111"); 12 | expect(lookup.response.results).to.deep.equal([]); 13 | }); 14 | }); -------------------------------------------------------------------------------- /tests/us_reverse_geo/test_Response.js: -------------------------------------------------------------------------------- 1 | const chai = require("chai"); 2 | const expect = chai.expect; 3 | const Response = require("../../src/us_reverse_geo/Response"); 4 | 5 | describe("A US Reverse Geo match response", function () { 6 | it("populates with the appropriate fields.", function () { 7 | const sampleResponse = { 8 | results: [ 9 | { 10 | coordinate: { 11 | latitude: 1.1, 12 | longitude: 2.2, 13 | accuracy: "3", 14 | license: 1 15 | }, 16 | distance: 4.4, 17 | address: { 18 | street: "5", 19 | city: "6", 20 | state_abbreviation: "7", 21 | zipcode: "8", 22 | source: "postal" 23 | } 24 | }, 25 | ] 26 | }; 27 | 28 | const response = new Response(sampleResponse); 29 | const result = response.results[0]; 30 | 31 | expect(result.distance).to.equal(4.4); 32 | let address = result.address; 33 | expect(address.street).to.equal("5"); 34 | expect(address.city).to.equal("6"); 35 | expect(address.state_abbreviation).to.equal("7"); 36 | expect(address.zipcode).to.equal("8"); 37 | expect(address.source).to.equal("postal"); 38 | let coordinate = result.coordinate; 39 | expect(coordinate.latitude).to.equal(1.1); 40 | expect(coordinate.longitude).to.equal(2.2); 41 | expect(coordinate.accuracy).to.equal("3"); 42 | expect(coordinate.license).to.equal("SmartyStreets Proprietary"); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /tests/us_street/test_Candidate.js: -------------------------------------------------------------------------------- 1 | const chai = require("chai"); 2 | const expect = chai.expect; 3 | const Candidate = require("../../src/us_street/Candidate"); 4 | 5 | describe("A match candidate", function () { 6 | it("has populated fields.", function () { 7 | const sampleResponse = { 8 | input_index: 0, 9 | candidate_index: 1, 10 | addressee: "2", 11 | delivery_line_1: "3", 12 | delivery_line_2: "4", 13 | last_line: "5", 14 | delivery_point_barcode: "6", 15 | smarty_key: "0000", 16 | components: { 17 | urbanization: "7", 18 | primary_number: "8", 19 | street_name: "9", 20 | street_predirection: "10", 21 | street_postdirection: "11", 22 | street_suffix: "12", 23 | secondary_number: "13", 24 | secondary_designator: "14", 25 | extra_secondary_number: "15", 26 | extra_secondary_designator: "16", 27 | pmb_designator: "17", 28 | pmb_number: "18", 29 | city_name: "19", 30 | default_city_name: "20", 31 | state_abbreviation: "21", 32 | zipcode: "22", 33 | plus4_code: "23", 34 | delivery_point: "24", 35 | delivery_point_check_digit: "25" 36 | }, 37 | metadata: { 38 | record_type: "26", 39 | zip_type: "27", 40 | county_fips: "28", 41 | county_name: "29", 42 | carrier_route: "30", 43 | congressional_district: "31", 44 | building_default_indicator: "32", 45 | rdi: "33", 46 | elot_sequence: "34", 47 | elot_sort: "35", 48 | latitude: 36.0, 49 | longitude: 37.0, 50 | coordinate_license: 1, 51 | precision: "38", 52 | time_zone: "39", 53 | utc_offset: 40.0, 54 | dst: "41", 55 | ews_match: "47" 56 | }, 57 | analysis: { 58 | dpv_match_code: "42", 59 | dpv_footnotes: "43", 60 | dpv_cmra: "44", 61 | dpv_vacant: "45", 62 | active: "46", 63 | footnotes: "48", 64 | lacslink_code: "49", 65 | lacslink_indicator: "50", 66 | suitelink_match: "51", 67 | dpv_no_stat: "52", 68 | enhanced_match: "53", 69 | } 70 | }; 71 | const candidate = new Candidate(sampleResponse); 72 | 73 | expect(candidate.inputIndex).to.equal(0); 74 | expect(candidate.candidateIndex).to.equal(1); 75 | expect(candidate.addressee).to.equal('2'); 76 | expect(candidate.deliveryLine1).to.equal('3'); 77 | expect(candidate.deliveryLine2).to.equal('4'); 78 | expect(candidate.lastLine).to.equal('5'); 79 | expect(candidate.deliveryPointBarcode).to.equal('6'); 80 | expect(candidate.smartyKey).to.equal('0000'); 81 | 82 | expect(candidate.components.urbanization).to.equal('7'); 83 | expect(candidate.components.primaryNumber).to.equal('8'); 84 | expect(candidate.components.streetName).to.equal('9'); 85 | expect(candidate.components.streetPredirection).to.equal('10'); 86 | expect(candidate.components.streetPostdirection).to.equal('11'); 87 | expect(candidate.components.streetSuffix).to.equal('12'); 88 | expect(candidate.components.secondaryNumber).to.equal('13'); 89 | expect(candidate.components.secondaryDesignator).to.equal('14'); 90 | expect(candidate.components.extraSecondaryNumber).to.equal('15'); 91 | expect(candidate.components.extraSecondaryDesignator).to.equal('16'); 92 | expect(candidate.components.pmbDesignator).to.equal('17'); 93 | expect(candidate.components.pmbNumber).to.equal('18'); 94 | expect(candidate.components.cityName).to.equal('19'); 95 | expect(candidate.components.defaultCityName).to.equal('20'); 96 | expect(candidate.components.state).to.equal('21'); 97 | expect(candidate.components.zipCode).to.equal('22'); 98 | expect(candidate.components.plus4Code).to.equal('23'); 99 | expect(candidate.components.deliveryPoint).to.equal('24'); 100 | expect(candidate.components.deliveryPointCheckDigit).to.equal('25'); 101 | 102 | expect(candidate.metadata.recordType).to.equal('26'); 103 | expect(candidate.metadata.zipType).to.equal('27'); 104 | expect(candidate.metadata.countyFips).to.equal('28'); 105 | expect(candidate.metadata.countyName).to.equal('29'); 106 | expect(candidate.metadata.carrierRoute).to.equal('30'); 107 | expect(candidate.metadata.congressionalDistrict).to.equal('31'); 108 | expect(candidate.metadata.buildingDefaultIndicator).to.equal('32'); 109 | expect(candidate.metadata.rdi).to.equal('33'); 110 | expect(candidate.metadata.elotSequence).to.equal('34'); 111 | expect(candidate.metadata.elotSort).to.equal('35'); 112 | expect(candidate.metadata.latitude).to.equal(36.0); 113 | expect(candidate.metadata.longitude).to.equal(37.0); 114 | expect(candidate.metadata.coordinateLicense).to.equal("SmartyStreets Proprietary"); 115 | expect(candidate.metadata.precision).to.equal('38'); 116 | expect(candidate.metadata.timeZone).to.equal('39'); 117 | expect(candidate.metadata.utcOffset).to.equal(40.0); 118 | expect(candidate.metadata.obeysDst).to.equal('41'); 119 | expect(candidate.metadata.isEwsMatch).to.equal('47'); 120 | 121 | expect(candidate.analysis.dpvMatchCode).to.equal('42'); 122 | expect(candidate.analysis.dpvFootnotes).to.equal('43'); 123 | expect(candidate.analysis.cmra).to.equal('44'); 124 | expect(candidate.analysis.vacant).to.equal('45'); 125 | expect(candidate.analysis.active).to.equal('46'); 126 | expect(candidate.analysis.footnotes).to.equal('48'); 127 | expect(candidate.analysis.lacsLinkCode).to.equal('49'); 128 | expect(candidate.analysis.lacsLinkIndicator).to.equal('50'); 129 | expect(candidate.analysis.isSuiteLinkMatch).to.equal('51'); 130 | expect(candidate.analysis.noStat).to.equal('52'); 131 | expect(candidate.analysis.enhancedMatch).to.equal('53'); 132 | }); 133 | }); -------------------------------------------------------------------------------- /tests/us_street/test_Client.js: -------------------------------------------------------------------------------- 1 | const chai = require("chai"); 2 | const expect = chai.expect; 3 | const Client = require("../../src/us_street/Client"); 4 | const ClientBuilder = require("../../src/ClientBuilder"); 5 | const Lookup = require("../../src/us_street/Lookup"); 6 | const Candidate = require("../../src/us_street/Candidate"); 7 | const Batch = require("../../src/Batch"); 8 | const errors = require("../../src/Errors"); 9 | const MockSender = require("../fixtures/mock_senders").MockSender; 10 | const MockSenderWithResponse = require("../fixtures/mock_senders").MockSenderWithResponse; 11 | 12 | describe("A US Street client", function () { 13 | it("calls its inner sender's send function.", function () { 14 | const mockSender = { 15 | send: function (request) { 16 | sentFlag = true; 17 | mockSenderRequest = request; 18 | } 19 | }; 20 | const client = new Client(mockSender); 21 | let lookup = new Lookup(); 22 | let sentFlag = false; 23 | let mockSenderRequest = {}; 24 | 25 | client.send(lookup); 26 | 27 | expect(sentFlag).to.equal(true); 28 | }); 29 | 30 | it("builds a request for a single lookup with the correct request parameters.", function () { 31 | let mockSender = new MockSender(); 32 | const client = new Client(mockSender); 33 | let lookup = new Lookup("1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"); 34 | let expectedParameters = { 35 | street: "1", 36 | street2: "2", 37 | secondary: "3", 38 | city: "4", 39 | state: "5", 40 | zipcode: "6", 41 | lastline: "7", 42 | addressee: "8", 43 | urbanization: "9", 44 | match: "10", 45 | candidates: "11", 46 | }; 47 | 48 | client.send(lookup); 49 | 50 | expect(mockSender.request.parameters).to.deep.equal(expectedParameters); 51 | }); 52 | 53 | it("defaults maxCandidates to 5 when match type is enhanced.", function () { 54 | let mockSender = new MockSender(); 55 | const client = new Client(mockSender); 56 | let lookup = new Lookup(); 57 | lookup.match = "enhanced"; 58 | let expectedParameters = { 59 | match: "enhanced", 60 | candidates: 5, 61 | }; 62 | 63 | client.send(lookup); 64 | 65 | expect(mockSender.request.parameters).to.deep.equal(expectedParameters); 66 | }); 67 | 68 | it("doesn't send an empty batch.", function () { 69 | let mockSender = new MockSender(); 70 | const client = new Client(mockSender); 71 | let batch = new Batch(); 72 | 73 | expect(() => client.send(batch)).to.throw(errors.BatchEmptyError); 74 | }); 75 | 76 | it("attaches a match candidate from a response to a lookup.", function () { 77 | const expectedMockPayload = [{delivery_line_1: "An address", input_index: 0}]; 78 | let mockSender = new MockSenderWithResponse(expectedMockPayload); 79 | const client = new Client(mockSender); 80 | let lookup = new Lookup(); 81 | let expectedResult = new Candidate({delivery_line_1: "An address", input_index: 0}); 82 | 83 | return client.send(lookup).then(response => { 84 | expect(lookup.result[0]).to.deep.equal(expectedResult); 85 | }); 86 | }); 87 | 88 | it("attaches match candidates to their corresponding lookups.", function () { 89 | const expectedMockPayload = JSON.stringify([ 90 | {delivery_line_1: "Address 0", input_index: 0}, 91 | {delivery_line_1: "Alternate address 0", input_index: 0}, 92 | {delivery_line_1: "Address 1", input_index: 1}, 93 | {delivery_line_1: "Address 3", input_index: 3}, 94 | ]); 95 | let mockSender = new MockSenderWithResponse(expectedMockPayload); 96 | let client = new Client(mockSender); 97 | let lookup0 = new Lookup(); 98 | let lookup1 = new Lookup(); 99 | let lookup2 = new Lookup(); 100 | let lookup3 = new Lookup(); 101 | let batch = new Batch(); 102 | 103 | batch.add(lookup0); 104 | batch.add(lookup1); 105 | batch.add(lookup2); 106 | batch.add(lookup3); 107 | 108 | client.send(batch).then(response => { 109 | expect(batch.getByIndex(0).result[0].deliveryLine1).to.equal("Address 0"); 110 | expect(batch.getByIndex(0).result[1].deliveryLine1).to.equal("Alternate address 0"); 111 | expect(batch.getByIndex(1).result[0].deliveryLine1).to.equal("Address 1"); 112 | expect(batch.getByIndex(2).result).to.deep.equal([]); 113 | expect(batch.getByIndex(3).result[0].deliveryLine1).to.equal("Address 3"); 114 | }); 115 | }); 116 | 117 | it("rejects with an exception if the response comes back with an error.", function () { 118 | const expectedMockError = new Error("Stamn! She's a tough one!"); 119 | let mockSender = new MockSenderWithResponse([], expectedMockError); 120 | let client = new Client(mockSender); 121 | let lookup = new Lookup(); 122 | 123 | return client.send(lookup).catch((e) => {expect(e).to.equal(expectedMockError);}); 124 | }); 125 | 126 | it("throws an exception if a lookup is undefined.", function () { 127 | let mockSender = new MockSender(); 128 | let client = new Client(mockSender); 129 | 130 | expect(() => client.send()).to.throw(errors.UndefinedLookupError); 131 | }); 132 | 133 | it("attaches request parameters for batches with a single lookup and a request payload for batches with more than 1 lookup.", function () { 134 | let mockSender = new MockSender(); 135 | let client = new Client(mockSender); 136 | let lookup1 = new Lookup("a"); 137 | let lookup2 = new Lookup("b"); 138 | let batch = new Batch(); 139 | 140 | batch.add(lookup1); 141 | client.send(batch); 142 | 143 | expect(mockSender.request.parameters).not.to.deep.equal({}); 144 | 145 | batch.add(lookup2); 146 | client.send(batch); 147 | 148 | expect(mockSender.request.payload).not.to.equal(undefined); 149 | }); 150 | }); 151 | -------------------------------------------------------------------------------- /tests/us_street/test_Lookup.js: -------------------------------------------------------------------------------- 1 | const chai = require("chai"); 2 | const expect = chai.expect; 3 | const Lookup = require("../../src/us_street/Lookup"); 4 | 5 | describe ("A US Street lookup", function () { 6 | it ("correctly populates fields.", function () { 7 | const lookup = new Lookup("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m"); 8 | 9 | expect(lookup.street).to.equal("a"); 10 | expect(lookup.street2).to.equal("b"); 11 | expect(lookup.secondary).to.equal("c"); 12 | expect(lookup.city).to.equal("d"); 13 | expect(lookup.state).to.equal("e"); 14 | expect(lookup.zipCode).to.equal("f"); 15 | expect(lookup.lastLine).to.equal("g"); 16 | expect(lookup.addressee).to.equal("h"); 17 | expect(lookup.urbanization).to.equal("i"); 18 | expect(lookup.match).to.equal("j"); 19 | expect(lookup.maxCandidates).to.equal("k"); 20 | expect(lookup.inputId).to.equal("l"); 21 | expect(lookup.format).to.equal("m"); 22 | }); 23 | 24 | it ("has a result array.", function () { 25 | const lookup = new Lookup(); 26 | 27 | expect(Array.isArray(lookup.result)).to.equal(true); 28 | }); 29 | }); -------------------------------------------------------------------------------- /tests/us_zipcode/test_Client.js: -------------------------------------------------------------------------------- 1 | let chai = require("chai"); 2 | const expect = chai.expect; 3 | const Client = require("../../src/us_zipcode/Client"); 4 | const Lookup = require("../../src/us_zipcode/Lookup"); 5 | const Result = require("../../src/us_zipcode/Result"); 6 | const Batch = require("../../src/Batch"); 7 | const errors = require("../../src/Errors"); 8 | const MockSender = require("../fixtures/mock_senders").MockSender; 9 | const MockSenderWithResponse = require("../fixtures/mock_senders").MockSenderWithResponse; 10 | 11 | describe("A US Zipcode client", function () { 12 | it("calls its inner sender's send function.", function () { 13 | const mockSender = { 14 | send: function (request) { 15 | sentFlag = true; 16 | mockSenderRequest = request; 17 | return new Promise((resolve, reject) => { 18 | }); 19 | } 20 | }; 21 | const client = new Client(mockSender); 22 | let lookup = new Lookup(); 23 | let sentFlag = false; 24 | let mockSenderRequest = {}; 25 | 26 | client.send(lookup); 27 | 28 | expect(sentFlag).to.equal(true); 29 | }); 30 | 31 | it("doesn't send an empty batch.", function () { 32 | let mockSender = new MockSender(); 33 | const client = new Client(mockSender); 34 | let batch = new Batch(); 35 | 36 | expect(() => client.send(batch)).to.throw(errors.BatchEmptyError); 37 | }); 38 | 39 | it("builds a request for a batch lookup with the correct JSON payload.", function () { 40 | let mockSender = new MockSender(); 41 | const client = new Client(mockSender); 42 | let lookup0 = new Lookup("lookup0"); 43 | let lookup1 = new Lookup("lookup1"); 44 | let lookup2 = new Lookup("lookup2"); 45 | let batch = new Batch(); 46 | const expectedPayload = [ 47 | {"city": "lookup0"}, 48 | {"city": "lookup1"}, 49 | {"city": "lookup2"} 50 | ]; 51 | 52 | batch.add(lookup0); 53 | batch.add(lookup1); 54 | batch.add(lookup2); 55 | 56 | client.send(batch); 57 | 58 | expect(mockSender.request.payload).to.deep.equal(expectedPayload); 59 | }); 60 | 61 | it("attaches a match candidate from a response to a lookup.", function () { 62 | const expectedMockPayload = [{input_index: 0}]; 63 | let mockSender = new MockSenderWithResponse(expectedMockPayload); 64 | const client = new Client(mockSender); 65 | let lookup = new Lookup(); 66 | let expectedResult = new Result({input_index: 0}); 67 | 68 | return client.send(lookup).then(response => { 69 | expect(lookup.result[0]).to.deep.equal(expectedResult); 70 | }); 71 | }); 72 | 73 | it("attaches match candidates to their corresponding lookups.", function () { 74 | const expectedMockPayload = JSON.stringify([ 75 | {city: "City 0", input_index: 0}, 76 | {city: "Alternate city 0", input_index: 0}, 77 | {city: "City 1", input_index: 1}, 78 | {city: "City 3", input_index: 3}, 79 | ]); 80 | let mockSender = new MockSenderWithResponse(expectedMockPayload); 81 | let client = new Client(mockSender); 82 | let lookup0 = new Lookup(); 83 | let lookup1 = new Lookup(); 84 | let lookup2 = new Lookup(); 85 | let lookup3 = new Lookup(); 86 | let batch = new Batch(); 87 | 88 | batch.add(lookup0); 89 | batch.add(lookup1); 90 | batch.add(lookup2); 91 | batch.add(lookup3); 92 | 93 | client.send(batch).then(response => { 94 | expect(batch.getByIndex(0).result[0].city).to.equal("City 0"); 95 | expect(batch.getByIndex(0).result[1].city).to.equal("Alternate city 0"); 96 | expect(batch.getByIndex(1).result[0].city).to.equal("City 1"); 97 | expect(batch.getByIndex(2).result).to.deep.equal([]); 98 | expect(batch.getByIndex(3).result[0].city).to.equal("City 3"); 99 | }); 100 | }); 101 | 102 | it("attaches request parameters for batches with a single lookup and a request payload for batches with more than 1 lookup.", function () { 103 | let mockSender = new MockSender(); 104 | let client = new Client(mockSender); 105 | let lookup1 = new Lookup("a"); 106 | let lookup2 = new Lookup("b"); 107 | let batch = new Batch(); 108 | 109 | batch.add(lookup1); 110 | client.send(batch); 111 | 112 | expect(mockSender.request.parameters).not.to.deep.equal({}); 113 | 114 | batch.add(lookup2); 115 | client.send(batch); 116 | 117 | expect(mockSender.request.payload).not.to.equal(undefined); 118 | }); 119 | 120 | it("rejects with an exception if the response comes back with an error.", function () { 121 | const expectedMockError = new Error("Stamn! She's a tough one!"); 122 | let mockSender = new MockSenderWithResponse([], expectedMockError); 123 | let client = new Client(mockSender); 124 | let lookup = new Lookup(); 125 | 126 | return client.send(lookup).catch((e) => {expect(e).to.equal(expectedMockError);}); 127 | }); 128 | 129 | it("throws an exception if a lookup is undefined.", function () { 130 | let mockSender = new MockSender(); 131 | let client = new Client(mockSender); 132 | 133 | expect(() => client.send()).to.throw(errors.UndefinedLookupError); 134 | }); 135 | 136 | it("builds a request for a single lookup with the correct request parameters.", function () { 137 | let mockSender = new MockSender(); 138 | const client = new Client(mockSender); 139 | let lookup = new Lookup("4", "5", "6"); 140 | let expectedParameters = { 141 | city: "4", 142 | state: "5", 143 | zipcode: "6", 144 | }; 145 | 146 | client.send(lookup); 147 | 148 | expect(mockSender.request.parameters).to.deep.equal(expectedParameters); 149 | }); 150 | }); 151 | -------------------------------------------------------------------------------- /tests/us_zipcode/test_Result.js: -------------------------------------------------------------------------------- 1 | const chai = require("chai"); 2 | const expect = chai.expect; 3 | const Result = require("../../src/us_zipcode/Result"); 4 | 5 | describe("A US Zipcode result", function () { 6 | it("populates accurately on a valid lookup.", function () { 7 | let sampleResponse = { 8 | "input_index": 0, 9 | "city_states": [ 10 | { 11 | "city": "1", 12 | "state_abbreviation": "2", 13 | "state": "3", 14 | "mailable_city": "4" 15 | } 16 | ], 17 | "zipcodes": [ 18 | { 19 | "zipcode": "5", 20 | "zipcode_type": "6", 21 | "default_city": "7", 22 | "county_fips": "8", 23 | "county_name": "9", 24 | "latitude": 10, 25 | "longitude": 11, 26 | "precision": "12", 27 | "alternate_counties": [ 28 | { 29 | "county_fips": "13", 30 | "county_name": "14", 31 | "state_abbreviation": "15", 32 | "state": "16" 33 | } 34 | ], 35 | "state_abbreviation": "17", 36 | "state": "18" 37 | } 38 | ] 39 | }; 40 | let result = new Result(sampleResponse); 41 | 42 | expect(result.inputIndex).to.equal(0); 43 | expect(result.status).to.equal(undefined); 44 | expect(result.reason).to.equal(undefined); 45 | 46 | expect(result.cities[0].city).to.equal('1'); 47 | expect(result.cities[0].stateAbbreviation).to.equal('2'); 48 | expect(result.cities[0].state).to.equal('3'); 49 | expect(result.cities[0].mailableCity).to.equal('4'); 50 | 51 | expect(result.zipcodes[0].zipcode).to.equal('5'); 52 | expect(result.zipcodes[0].zipcodeType).to.equal('6'); 53 | expect(result.zipcodes[0].defaultCity).to.equal('7'); 54 | expect(result.zipcodes[0].countyFips).to.equal('8'); 55 | expect(result.zipcodes[0].countyName).to.equal('9'); 56 | expect(result.zipcodes[0].latitude).to.equal(10); 57 | expect(result.zipcodes[0].longitude).to.equal(11); 58 | expect(result.zipcodes[0].precision).to.equal('12'); 59 | expect(result.zipcodes[0].alternateCounties[0].countyFips).to.equal('13'); 60 | expect(result.zipcodes[0].alternateCounties[0].countyName).to.equal('14'); 61 | expect(result.zipcodes[0].alternateCounties[0].stateAbbreviation).to.equal('15'); 62 | expect(result.zipcodes[0].alternateCounties[0].state).to.equal('16'); 63 | expect(result.zipcodes[0].stateAbbreviation).to.equal('17'); 64 | expect(result.zipcodes[0].state).to.equal('18'); 65 | 66 | expect(result.valid).to.equal(true); 67 | }); 68 | 69 | it("populates accurately on an invalid lookup", function () { 70 | let sampleResponse = { 71 | "status": "testing_status", 72 | "reason": "We are testing." 73 | }; 74 | 75 | let result = new Result(sampleResponse); 76 | 77 | expect(result.status).to.equal("testing_status"); 78 | expect(result.reason).to.equal("We are testing."); 79 | 80 | }); 81 | }); --------------------------------------------------------------------------------