├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ ├── ci.yml │ └── docs.yml ├── .gitignore ├── .tool-versions ├── LICENSE ├── README.md ├── jasmine.json ├── karma.conf.js ├── package-lock.json ├── package.json ├── rollup.config.js ├── src ├── api │ ├── billing │ │ ├── index.ts │ │ ├── invoices.ts │ │ ├── plan-families.ts │ │ ├── plans.ts │ │ ├── subscriptions.ts │ │ ├── transactions.ts │ │ └── usage.ts │ ├── crm │ │ ├── accounts.ts │ │ ├── activities.ts │ │ ├── deals.ts │ │ ├── index.ts │ │ └── people.ts │ ├── marketing │ │ ├── email-list-subscriptions.ts │ │ └── index.ts │ ├── support │ │ ├── cases.ts │ │ └── index.ts │ └── user │ │ ├── index.ts │ │ ├── password.ts │ │ └── profile.ts ├── index.ts ├── models │ ├── billing │ │ ├── add-on.ts │ │ ├── billing-add-on-type.ts │ │ ├── billing-renewal-term.ts │ │ ├── billing-transaction-type.ts │ │ ├── charge-summary.ts │ │ ├── invoice-display-item.ts │ │ ├── invoice-line-item.ts │ │ ├── invoice.ts │ │ ├── plan-add-on.ts │ │ ├── plan-family.ts │ │ ├── plan.ts │ │ ├── subscription-add-on.ts │ │ ├── subscription.ts │ │ ├── transaction.ts │ │ └── usage-item.ts │ ├── crm │ │ ├── account-stage.ts │ │ ├── account.ts │ │ ├── activity-type.ts │ │ ├── activity.ts │ │ ├── deal-person.ts │ │ ├── deal-pipeline-stage.ts │ │ ├── deal-pipeline.ts │ │ ├── deal.ts │ │ ├── person-account.ts │ │ └── person.ts │ ├── marketing │ │ ├── email-list-person.ts │ │ └── email-list.ts │ ├── shared │ │ ├── address.ts │ │ └── entity-type.ts │ ├── support │ │ ├── case-history.ts │ │ ├── case-source.ts │ │ ├── case-status.ts │ │ └── case.ts │ └── wrappers │ │ ├── list.ts │ │ └── validation-error.ts └── util │ ├── credentials.ts │ ├── request.ts │ └── store.ts ├── test ├── api │ ├── billing │ │ ├── index.spec.ts │ │ ├── invoices │ │ │ └── add.spec.ts │ │ ├── plan-families │ │ │ └── get-all.spec.ts │ │ ├── plans │ │ │ └── get-all.spec.ts │ │ ├── subscriptions │ │ │ ├── add.spec.ts │ │ │ ├── change-trial-to-subscribed.spec.ts │ │ │ ├── get-all.spec.ts │ │ │ ├── get.spec.ts │ │ │ ├── preview-add.spec.ts │ │ │ ├── preview-update.spec.ts │ │ │ ├── set-subscription-upgrade-required.spec.ts │ │ │ └── update.spec.ts │ │ ├── transactions │ │ │ └── get-all.spec.ts │ │ └── usage │ │ │ ├── add.spec.ts │ │ │ └── get-all.spec.ts │ ├── crm │ │ ├── accounts │ │ │ ├── add.spec.ts │ │ │ ├── cancel.spec.ts │ │ │ ├── delete.spec.ts │ │ │ ├── expire-current-subscription.spec.ts │ │ │ ├── extend-trial.spec.ts │ │ │ ├── get-all.spec.ts │ │ │ ├── get.spec.ts │ │ │ ├── remove-cancellation.spec.ts │ │ │ └── update.spec.ts │ │ ├── activities │ │ │ ├── add.spec.ts │ │ │ └── get-all.spec.ts │ │ ├── deals │ │ │ ├── add.spec.ts │ │ │ ├── delete.spec.ts │ │ │ ├── get-all.spec.ts │ │ │ ├── get.spec.ts │ │ │ └── update.spec.ts │ │ ├── index.spec.ts │ │ └── people │ │ │ ├── add.spec.ts │ │ │ ├── delete.spec.ts │ │ │ ├── get-all.spec.ts │ │ │ ├── get.spec.ts │ │ │ └── update.spec.ts │ ├── marketing │ │ ├── email-list-subscriptions │ │ │ ├── add.spec.ts │ │ │ ├── delete.spec.ts │ │ │ └── get-all.spec.ts │ │ └── index.spec.ts │ ├── support │ │ ├── cases │ │ │ ├── add-reply-from-agent.spec.ts │ │ │ ├── add-reply-from-client.spec.ts │ │ │ ├── add.spec.ts │ │ │ └── get-all.spec.ts │ │ └── index.spec.ts │ └── user │ │ ├── impersonate.spec.ts │ │ ├── index.spec.ts │ │ ├── login.spec.ts │ │ ├── password │ │ └── update.spec.ts │ │ └── profile │ │ ├── get.spec.ts │ │ └── update.spec.ts ├── index.spec.ts └── util │ ├── credentials.spec.ts │ └── request.spec.ts ├── tsconfig.cjs.json ├── tsconfig.esm.json ├── tsconfig.json ├── tsconfig.spec.json └── typedoc.json /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | coverage 4 | /rollup.config.js 5 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | plugins: [ 5 | '@typescript-eslint', 6 | 'import' 7 | ], 8 | extends: [ 9 | 'eslint:recommended', 10 | 'plugin:@typescript-eslint/recommended', 11 | ], 12 | rules: { 13 | 'no-console': 'error', 14 | 'eol-last': 'error', 15 | 'import/no-default-export': 'error', 16 | "@typescript-eslint/semi": ["error"] 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | 6 | env: 7 | node_version: '12' 8 | jobs: 9 | lint: 10 | runs-on: ubuntu-latest 11 | timeout-minutes: 5 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions/setup-node@v1 15 | with: 16 | node-version: ${{ env.node_version }} 17 | - uses: actions/cache@v2 18 | with: 19 | path: ~/.npm 20 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 21 | restore-keys: | 22 | ${{ runner.os }}-node- 23 | - run: npm ci 24 | - run: npm run lint 25 | test: 26 | runs-on: ubuntu-latest 27 | timeout-minutes: 10 28 | steps: 29 | - uses: actions/checkout@v2 30 | - uses: actions/setup-node@v1 31 | with: 32 | node-version: ${{ env.node_version }} 33 | - uses: actions/cache@v2 34 | with: 35 | path: ~/.npm 36 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 37 | restore-keys: | 38 | ${{ runner.os }}-node- 39 | - run: npm ci 40 | - run: npm run test 41 | - uses: codacy/codacy-coverage-reporter-action@master 42 | with: 43 | project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} 44 | coverage-reports: coverage/outseta-api-client/lcov.info -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Docs 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | 7 | env: 8 | node_version: '12' 9 | jobs: 10 | generate_and_publish: 11 | runs-on: ubuntu-latest 12 | timeout-minutes: 5 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions/setup-node@v1 16 | with: 17 | node-version: ${{ env.node_version }} 18 | - uses: actions/cache@v2 19 | with: 20 | path: ~/.npm 21 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 22 | restore-keys: | 23 | ${{ runner.os }}-node- 24 | - run: npm ci 25 | - run: npm run build:docs 26 | - uses: peaceiris/actions-gh-pages@v3 27 | with: 28 | deploy_key: ${{ secrets.ACTIONS_DEPLOY_KEY }} 29 | publish_dir: ./docs 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/node,macos,windows,webstorm+all,vscode 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=node,macos,windows,webstorm+all,vscode 4 | 5 | ### macOS ### 6 | # General 7 | .DS_Store 8 | .AppleDouble 9 | .LSOverride 10 | 11 | # Icon must end with two \r 12 | Icon 13 | 14 | 15 | # Thumbnails 16 | ._* 17 | 18 | # Files that might appear in the root of a volume 19 | .DocumentRevisions-V100 20 | .fseventsd 21 | .Spotlight-V100 22 | .TemporaryItems 23 | .Trashes 24 | .VolumeIcon.icns 25 | .com.apple.timemachine.donotpresent 26 | 27 | # Directories potentially created on remote AFP share 28 | .AppleDB 29 | .AppleDesktop 30 | Network Trash Folder 31 | Temporary Items 32 | .apdisk 33 | 34 | ### Node ### 35 | # Logs 36 | logs 37 | *.log 38 | npm-debug.log* 39 | yarn-debug.log* 40 | yarn-error.log* 41 | lerna-debug.log* 42 | 43 | # Diagnostic reports (https://nodejs.org/api/report.html) 44 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 45 | 46 | # Runtime data 47 | pids 48 | *.pid 49 | *.seed 50 | *.pid.lock 51 | 52 | # Directory for instrumented libs generated by jscoverage/JSCover 53 | lib-cov 54 | 55 | # Coverage directory used by tools like istanbul 56 | coverage 57 | *.lcov 58 | 59 | # nyc test coverage 60 | .nyc_output 61 | 62 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 63 | .grunt 64 | 65 | # Bower dependency directory (https://bower.io/) 66 | bower_components 67 | 68 | # node-waf configuration 69 | .lock-wscript 70 | 71 | # Compiled binary addons (https://nodejs.org/api/addons.html) 72 | build/Release 73 | 74 | # Dependency directories 75 | node_modules/ 76 | jspm_packages/ 77 | 78 | # TypeScript v1 declaration files 79 | typings/ 80 | 81 | # TypeScript cache 82 | *.tsbuildinfo 83 | 84 | # Optional npm cache directory 85 | .npm 86 | 87 | # Optional eslint cache 88 | .eslintcache 89 | 90 | # Microbundle cache 91 | .rpt2_cache/ 92 | .rts2_cache_cjs/ 93 | .rts2_cache_es/ 94 | .rts2_cache_umd/ 95 | 96 | # Optional REPL history 97 | .node_repl_history 98 | 99 | # Output of 'npm pack' 100 | *.tgz 101 | 102 | # Yarn Integrity file 103 | .yarn-integrity 104 | 105 | # dotenv environment variables file 106 | .env 107 | .env.test 108 | .env*.local 109 | 110 | # parcel-bundler cache (https://parceljs.org/) 111 | .cache 112 | .parcel-cache 113 | 114 | # Next.js build output 115 | .next 116 | 117 | # Nuxt.js build / generate output 118 | .nuxt 119 | dist 120 | 121 | # Gatsby files 122 | .cache/ 123 | # Comment in the public line in if your project uses Gatsby and not Next.js 124 | # https://nextjs.org/blog/next-9-1#public-directory-support 125 | # public 126 | 127 | # vuepress build output 128 | .vuepress/dist 129 | 130 | # Serverless directories 131 | .serverless/ 132 | 133 | # FuseBox cache 134 | .fusebox/ 135 | 136 | # DynamoDB Local files 137 | .dynamodb/ 138 | 139 | # TernJS port file 140 | .tern-port 141 | 142 | # Stores VSCode versions used for testing VSCode extensions 143 | .vscode-test 144 | 145 | ### vscode ### 146 | .vscode/* 147 | !.vscode/settings.json 148 | !.vscode/tasks.json 149 | !.vscode/launch.json 150 | !.vscode/extensions.json 151 | *.code-workspace 152 | 153 | ### WebStorm+all ### 154 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 155 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 156 | 157 | # User-specific stuff 158 | .idea/**/workspace.xml 159 | .idea/**/tasks.xml 160 | .idea/**/usage.statistics.xml 161 | .idea/**/dictionaries 162 | .idea/**/shelf 163 | 164 | # Generated files 165 | .idea/**/contentModel.xml 166 | 167 | # Sensitive or high-churn files 168 | .idea/**/dataSources/ 169 | .idea/**/dataSources.ids 170 | .idea/**/dataSources.local.xml 171 | .idea/**/sqlDataSources.xml 172 | .idea/**/dynamic.xml 173 | .idea/**/uiDesigner.xml 174 | .idea/**/dbnavigator.xml 175 | 176 | # Gradle 177 | .idea/**/gradle.xml 178 | .idea/**/libraries 179 | 180 | # Gradle and Maven with auto-import 181 | # When using Gradle or Maven with auto-import, you should exclude module files, 182 | # since they will be recreated, and may cause churn. Uncomment if using 183 | # auto-import. 184 | # .idea/artifacts 185 | # .idea/compiler.xml 186 | # .idea/jarRepositories.xml 187 | # .idea/modules.xml 188 | # .idea/*.iml 189 | # .idea/modules 190 | # *.iml 191 | # *.ipr 192 | 193 | # CMake 194 | cmake-build-*/ 195 | 196 | # Mongo Explorer plugin 197 | .idea/**/mongoSettings.xml 198 | 199 | # File-based project format 200 | *.iws 201 | 202 | # IntelliJ 203 | out/ 204 | 205 | # mpeltonen/sbt-idea plugin 206 | .idea_modules/ 207 | 208 | # JIRA plugin 209 | atlassian-ide-plugin.xml 210 | 211 | # Cursive Clojure plugin 212 | .idea/replstate.xml 213 | 214 | # Crashlytics plugin (for Android Studio and IntelliJ) 215 | com_crashlytics_export_strings.xml 216 | crashlytics.properties 217 | crashlytics-build.properties 218 | fabric.properties 219 | 220 | # Editor-based Rest Client 221 | .idea/httpRequests 222 | 223 | # Android studio 3.1+ serialized cache file 224 | .idea/caches/build_file_checksums.ser 225 | 226 | ### WebStorm+all Patch ### 227 | # Ignores the whole .idea folder and all .iml files 228 | # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 229 | 230 | .idea/ 231 | 232 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 233 | 234 | *.iml 235 | modules.xml 236 | .idea/misc.xml 237 | *.ipr 238 | 239 | # Sonarlint plugin 240 | .idea/sonarlint 241 | 242 | ### Windows ### 243 | # Windows thumbnail cache files 244 | Thumbs.db 245 | Thumbs.db:encryptable 246 | ehthumbs.db 247 | ehthumbs_vista.db 248 | 249 | # Dump file 250 | *.stackdump 251 | 252 | # Folder config file 253 | [Dd]esktop.ini 254 | 255 | # Recycle Bin used on file shares 256 | $RECYCLE.BIN/ 257 | 258 | # Windows Installer files 259 | *.cab 260 | *.msi 261 | *.msix 262 | *.msm 263 | *.msp 264 | 265 | # Windows shortcuts 266 | *.lnk 267 | 268 | # End of https://www.toptal.com/developers/gitignore/api/node,macos,windows,webstorm+all,vscode 269 | 270 | docs/ 271 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | nodejs 12.19.0 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 TiltCamp 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Outseta REST API Client 2 | 3 | [![Build Status](https://img.shields.io/github/workflow/status/tiltcamp/outseta-api-client/CI/main)](https://github.com/tiltcamp/outseta-api-client/actions?query=branch%3Amain) 4 | [![Test Coverage](https://img.shields.io/codacy/coverage/e981251e6d9c4fb0a201c5e4adaebf9f/main)](https://app.codacy.com/gh/tiltcamp/outseta-api-client/dashboard) 5 | [![Code Quality Grade](https://img.shields.io/codacy/grade/e981251e6d9c4fb0a201c5e4adaebf9f/main)](https://app.codacy.com/gh/tiltcamp/outseta-api-client/dashboard) 6 | [![Latest Version](https://img.shields.io/npm/v/outseta-api-client)](https://www.npmjs.com/package/outseta-api-client) 7 | [![NPM Bundle Size](https://img.shields.io/bundlephobia/minzip/outseta-api-client)](https://www.npmjs.com/package/outseta-api-client) 8 | [![License](https://img.shields.io/github/license/tiltcamp/outseta-api-client)](https://github.com/tiltcamp/outseta-api-client/blob/main/LICENSE) 9 | 10 | This is a typed API client for [Outseta](https://www.outseta.com/) written in TypeScript. The only dependency is 11 | `fetch`, so it works in the browser and works with Node if `fetch` is [polyfilled](https://github.com/node-fetch/node-fetch). 12 | See below for an example. 13 | 14 | This package implements all the endpoints that are [publicly documented](https://documenter.getpostman.com/view/3613332/outseta-rest-api-v1/7TNfr6k), 15 | and even includes a few extras. Most issues are likely to be related to incorrect types: attributes missing "optional" 16 | flags in models, "unknown" types in the models, or missing filters that could be added. I've done my best to infer 17 | the contents for these models, but as it was done entirely via conjecture there are bound to be some incorrect 18 | assumptions. If you catch any of these (or anything else for that matter), please submit an issue or feel free to 19 | submit a PR. 20 | 21 | I am also open to adding more endpoints - there are plenty of undocumented endpoints I can see being useful. As with the 22 | above, issues and PRs are welcome. 23 | 24 | ## Quick Start 25 | 26 | ### Installing 27 | NPM: 28 | ```shell 29 | npm install outseta-api-client --save 30 | ``` 31 | 32 | Yarn: 33 | ```shell 34 | yarn add outseta-api-client 35 | ``` 36 | ### Importing 37 | ES6 import: 38 | ```typescript 39 | import OutsetaApiClient from 'outseta-api-client'; 40 | ``` 41 | 42 | CommonJS require: 43 | ```javascript 44 | var OutsetaApiClient = require('outseta-api-client').default; 45 | ``` 46 | 47 | Additionally, NodeJS requires a polyfill for `fetch`: 48 | ```javascript 49 | globalThis.fetch = require('node-fetch'); 50 | var OutsetaApiClient = require('outseta-api-client').default; 51 | ``` 52 | 53 | ### Initialization 54 | 55 | In the following examples, the subdomain is the beginning of your Outseta domain - so if your Outseta domain 56 | is `test-company.outseta.com`, it would be just `test-company` as seen below. 57 | 58 | #### Initializing without any keys: 59 | ```typescript 60 | const client = new OutsetaApiClient({ subdomain: 'test-company' }); 61 | ``` 62 | #### Initializing with server-side API keys: 63 | ```typescript 64 | const client = new OutsetaApiClient({ 65 | subdomain: 'test-company', 66 | apiKey: example_key, 67 | secretKey: example_secret 68 | }); 69 | ``` 70 | 71 | #### Initializing with a user access token: 72 | ```typescript 73 | const client = new OutsetaApiClient({ 74 | subdomain: 'test-company', 75 | accessToken: jwt_user_token 76 | }); 77 | ``` 78 | 79 | ## Resources & Documentation 80 | #### Billing 81 | - [Invoices](https://tiltcamp.github.io/outseta-api-client/classes/api_billing_invoices.invoices.html#add) 82 | - [Plans](https://tiltcamp.github.io/outseta-api-client/classes/api_billing_plans.plans.html#getall) 83 | - [Plan Families](https://tiltcamp.github.io/outseta-api-client/classes/api_billing_plan_families.planfamilies.html#getall) 84 | - [Subscriptions](https://tiltcamp.github.io/outseta-api-client/classes/api_billing_subscriptions.subscriptions.html#add) 85 | - [Transactions](https://tiltcamp.github.io/outseta-api-client/classes/api_billing_transactions.transactions.html#getall) 86 | - [Usage](https://tiltcamp.github.io/outseta-api-client/classes/api_billing_usage.usage.html#add) 87 | 88 | #### CRM 89 | - [Accounts](https://tiltcamp.github.io/outseta-api-client/classes/api_crm_accounts.accounts.html#add) 90 | - [Activities](https://tiltcamp.github.io/outseta-api-client/classes/api_crm_activities.activities.html#add) 91 | - [Deals](https://tiltcamp.github.io/outseta-api-client/classes/api_crm_deals.deals.html#add) 92 | - [People](https://tiltcamp.github.io/outseta-api-client/classes/api_crm_people.people.html#add) 93 | 94 | #### Marketing 95 | - [Email List Subscriptions](https://tiltcamp.github.io/outseta-api-client/classes/api_marketing_email_list_subscriptions.emaillistsubscriptions.html#add) 96 | 97 | #### Support 98 | - [Cases](https://tiltcamp.github.io/outseta-api-client/classes/api_support_cases.cases.html#add) 99 | 100 | #### User 101 | - [Impersonate](https://tiltcamp.github.io/outseta-api-client/classes/api_user.user.html#impersonate) 102 | - [Login](https://tiltcamp.github.io/outseta-api-client/classes/api_user.user.html#login) 103 | - [Password](https://tiltcamp.github.io/outseta-api-client/classes/api_user_password.password.html#update) 104 | - [Profile](https://tiltcamp.github.io/outseta-api-client/classes/api_user_profile.profile.html#get) 105 | -------------------------------------------------------------------------------- /jasmine.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "test", 3 | "spec_files": [ 4 | "**/*.ts" 5 | ], 6 | "helpers": [ 7 | "../node_modules/ts-node/register/type-check.js" 8 | ], 9 | "stopSpecOnExpectationFailure": false, 10 | "random": true 11 | } 12 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: './', 7 | frameworks: ['jasmine', 'karma-typescript'], 8 | plugins: [ 9 | 'karma-typescript', 10 | 'karma-jasmine', 11 | 'karma-chrome-launcher', 12 | 'karma-jasmine-html-reporter' 13 | ], 14 | karmaTypescriptConfig: { 15 | tsconfig: "./tsconfig.spec.json", 16 | coverageOptions: { 17 | threshold: { 18 | file: { 19 | statements: 100, 20 | lines: 100, 21 | branches: 100, 22 | functions: 100 23 | } 24 | } 25 | }, 26 | reports: { 27 | html: { 28 | directory: 'coverage', 29 | subdirectory: 'outseta-api-client', 30 | filename: 'index.html' 31 | }, 32 | lcovonly: { 33 | directory: 'coverage', 34 | subdirectory: 'outseta-api-client', 35 | filename: 'lcov.info' 36 | }, 37 | 'text-summary': null 38 | }, 39 | }, 40 | files: [ 41 | { pattern: 'src/**/*.ts' }, 42 | { pattern: 'test/**/*.spec.ts' } 43 | ], 44 | preprocessors: { 45 | 'src/**/*.ts': [ 'karma-typescript' ], 46 | 'test/**/*.spec.ts': [ 'karma-typescript' ] 47 | }, 48 | reporters: ['progress', 'kjhtml', 'karma-typescript'], 49 | colors: true, 50 | logLevel: config.LOG_INFO, 51 | browsers: ['Chrome'] 52 | }); 53 | }; 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "outseta-api-client", 3 | "version": "1.1.0", 4 | "description": "An API client for Outseta.", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "module": "dist/index.mjs", 8 | "files": [ 9 | "dist/**/*" 10 | ], 11 | "scripts": { 12 | "lint": "npx eslint ./src ./test --ext .ts,.js --max-warnings 0", 13 | "build:cjs": "npx tsc --project tsconfig.cjs.json", 14 | "build:esm": "npx rollup -c", 15 | "build": "rm -rf dist/ && npm run build:esm && npm run build:cjs", 16 | "build:docs": "npx typedoc", 17 | "test": "npx karma start karma.conf.js --single-run --browsers=ChromeHeadless", 18 | "test:watch": "npx karma start karma.conf.js --auto-watch", 19 | "prepare": "npm run build", 20 | "prepublishOnly": "npm test && npm run lint", 21 | "postpublish": "git push && git push --tags" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/tiltcamp/outseta-api-client.git" 26 | }, 27 | "author": "TiltCamp", 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/tiltcamp/outseta-api-client/issues" 31 | }, 32 | "homepage": "https://github.com/tiltcamp/outseta-api-client#readme", 33 | "devDependencies": { 34 | "@types/jasmine": "^3.6.11", 35 | "@typescript-eslint/eslint-plugin": "^4.22.1", 36 | "@typescript-eslint/parser": "^4.22.1", 37 | "eslint": "^7.25.0", 38 | "eslint-plugin-import": "^2.22.1", 39 | "jasmine": "^3.7.0", 40 | "jasmine-spec-reporter": "^6.0.0", 41 | "karma": "^6.3.2", 42 | "karma-chrome-launcher": "^3.1.0", 43 | "karma-jasmine": "^4.0.1", 44 | "karma-jasmine-html-reporter": "^1.5.4", 45 | "karma-typescript": "^5.5.1", 46 | "pretender": "^3.4.3", 47 | "rollup": "^2.48.0", 48 | "rollup-plugin-typescript2": "^0.30.0", 49 | "typedoc": "^0.20.36", 50 | "typescript": "^4.2.4" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import typescript from 'rollup-plugin-typescript2'; 2 | 3 | // https://github.com/microsoft/TypeScript/issues/18442#issuecomment-749896695 4 | export default [ 5 | { 6 | preserveModules: true, 7 | input: ['src/index.ts'], 8 | output: [{ 9 | dir: 'dist', 10 | format: 'esm', 11 | entryFileNames: '[name].mjs', 12 | preserveModules: true 13 | }], 14 | plugins: [ 15 | typescript({ 16 | tsconfig: './tsconfig.esm.json' 17 | }) 18 | ], 19 | } 20 | ]; 21 | -------------------------------------------------------------------------------- /src/api/billing/index.ts: -------------------------------------------------------------------------------- 1 | import { Store } from '../../util/store'; 2 | import { Plans } from './plans'; 3 | import { Invoices } from './invoices'; 4 | import { Subscriptions } from './subscriptions'; 5 | import { PlanFamilies } from './plan-families'; 6 | import { Transactions } from './transactions'; 7 | import { Usage } from './usage'; 8 | 9 | export class Billing { 10 | public readonly invoices: Invoices; 11 | public readonly plans: Plans; 12 | public readonly planFamilies: PlanFamilies; 13 | public readonly subscriptions: Subscriptions; 14 | public readonly transactions: Transactions; 15 | public readonly usage: Usage; 16 | 17 | constructor(store: Store) { 18 | this.invoices = new Invoices(store); 19 | this.plans = new Plans(store); 20 | this.planFamilies = new PlanFamilies(store); 21 | this.subscriptions = new Subscriptions(store); 22 | this.transactions = new Transactions(store); 23 | this.usage = new Usage(store); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/api/billing/invoices.ts: -------------------------------------------------------------------------------- 1 | import { Store } from '../../util/store'; 2 | import { ValidationError } from '../../models/wrappers/validation-error'; 3 | import { Request } from '../../util/request'; 4 | import { Invoice } from '../../models/billing/invoice'; 5 | import { Subscription } from '../../models/billing/subscription'; 6 | import { InvoiceLineItem } from '../../models/billing/invoice-line-item'; 7 | 8 | export class Invoices { 9 | private readonly store: Store; 10 | 11 | constructor(store: Store) { 12 | this.store = store; 13 | } 14 | 15 | /** 16 | * Manually add an invoice to a subscription. Can be used for manually billing an account. 17 | * ```typescript 18 | * const client = new OutsetaApiClient({ 19 | * subdomain: 'test-company', 20 | * apiKey: example_key, 21 | * secretKey: example_secret 22 | * }); 23 | * const response = await client.billing.subscriptions.add({ 24 | * "Subscription": { 25 | * "Uid": "dQGxEz94" 26 | * }, 27 | * "InvoiceDate": new Date('12/29/2021'), 28 | * "InvoiceLineItems": [ 29 | * { 30 | * "Description": "Example Item", 31 | * "Amount": 50 32 | * } 33 | * ] 34 | * }); 35 | * console.log(response); 36 | * ``` 37 | * 38 | * @param invoice The invoice to add to Outseta. 39 | * @returns The response body if response status OK, or response body of validation errors if response status 400. 40 | * @throws [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) If the server returns a 41 | * non-"OK" status, the whole response object will be thrown. 42 | */ 43 | public async add(invoice: InvoiceAdd): Promise> { 44 | const request = new Request(this.store, 'billing/invoices') 45 | .authenticateAsServer() 46 | .withBody(invoice); 47 | const response = await request.post(); 48 | 49 | if (response.status === 400) 50 | return await response.json() as ValidationError; 51 | else if (response.ok) 52 | return await response.json() as Invoice; 53 | else throw response; 54 | } 55 | } 56 | 57 | export interface InvoiceAdd extends Partial> { 58 | [key: string]: unknown; 59 | Subscription: Required> & Partial; 60 | InvoiceDate: Date; 61 | InvoiceLineItems: Array> & Partial> 62 | } 63 | -------------------------------------------------------------------------------- /src/api/billing/plan-families.ts: -------------------------------------------------------------------------------- 1 | import { PlanFamily } from '../../models/billing/plan-family'; 2 | import { Request } from '../../util/request'; 3 | import { Store } from '../../util/store'; 4 | import { List } from '../../models/wrappers/list'; 5 | 6 | export class PlanFamilies { 7 | private readonly store: Store; 8 | 9 | constructor(store: Store) { 10 | this.store = store; 11 | } 12 | 13 | /** 14 | * Get all available plan families. 15 | * 16 | * ```typescript 17 | * const client = new OutsetaApiClient({ subdomain: 'test-company' }); 18 | * const response = await client.billing.planFamilies.getAll(); 19 | * console.log(response); 20 | * ``` 21 | * 22 | * @param options.limit The number of results returned by the API. 23 | * @param options.offset For pagination; returns (limit) results after this value. 24 | * @returns The response body. 25 | * @throws [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) If the server returns a 26 | * non-"OK" status, the whole response object will be thrown. 27 | */ 28 | public async getAll(options: { 29 | limit?: number, 30 | offset?: number 31 | } = {}): Promise> { 32 | const request = new Request(this.store, 'billing/planfamilies'); 33 | if (options.limit) request.withParams({ limit: `${options.limit}` }); 34 | if (options.offset) request.withParams({ offset: `${options.offset}` }); 35 | 36 | const response = await request.get(); 37 | 38 | if (!response.ok) throw response; 39 | return await response.json() as List; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/api/billing/plans.ts: -------------------------------------------------------------------------------- 1 | import { PlanFamily } from '../../models/billing/plan-family'; 2 | import { Request } from '../../util/request'; 3 | import { Store } from '../../util/store'; 4 | import { List } from '../../models/wrappers/list'; 5 | import { Plan } from '../../models/billing/plan'; 6 | 7 | export class Plans { 8 | private readonly store: Store; 9 | 10 | constructor(store: Store) { 11 | this.store = store; 12 | } 13 | 14 | /** 15 | * Get all available plans. 16 | * 17 | * ```typescript 18 | * const client = new OutsetaApiClient({ subdomain: 'test-company' }); 19 | * const response = await client.billing.plans.getAll(); 20 | * console.log(response); 21 | * ``` 22 | * 23 | * @param options.limit The number of results returned by the API. 24 | * @param options.offset For pagination; returns (limit) results after this value. 25 | * @param options.PlanFamily Get all plans that belong to a specific plan family. 26 | * @returns The response body. 27 | * @throws [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) If the server returns a 28 | * non-"OK" status, the whole response object will be thrown. 29 | */ 30 | public async getAll(options: { 31 | limit?: number, 32 | offset?: number, 33 | PlanFamily?: Required> 34 | } = {}): Promise> { 35 | const request = new Request(this.store, 'billing/plans'); 36 | if (options.limit) request.withParams({ limit: `${options.limit}` }); 37 | if (options.offset) request.withParams({ offset: `${options.offset}` }); 38 | if (options.PlanFamily) request.withParams({ 'PlanFamily.Uid': options.PlanFamily.Uid }); 39 | 40 | const response = await request.get(); 41 | 42 | if (!response.ok) throw response; 43 | return await response.json() as List; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/api/billing/transactions.ts: -------------------------------------------------------------------------------- 1 | import { Transaction } from '../../models/billing/transaction'; 2 | import { List } from '../../models/wrappers/list'; 3 | import { Request } from '../../util/request'; 4 | import { Store } from '../../util/store'; 5 | 6 | export class Transactions { 7 | private readonly store: Store; 8 | 9 | constructor(store: Store) { 10 | this.store = store; 11 | } 12 | 13 | /** 14 | * Get all transactions for a particular account by its uid. 15 | * 16 | * ```typescript 17 | * const client = new OutsetaApiClient({ subdomain: 'test-company' }); 18 | * const response = await client.billing.transactions.getAll(accountUid); 19 | * console.log(response); 20 | * ``` 21 | * 22 | * @param accountUid The uid for the account to grab transactions from. 23 | * @returns The response body. 24 | * @throws [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) If the server returns a 25 | * non-"OK" status, the whole response object will be thrown. 26 | */ 27 | public async getAll(accountUid: string): Promise> { 28 | const request = new Request(this.store, `billing/transactions/${accountUid}`).authenticateAsServer(); 29 | const response = await request.get(); 30 | 31 | if (!response.ok) throw response; 32 | return await response.json() as List; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/api/billing/usage.ts: -------------------------------------------------------------------------------- 1 | import { Account } from '../../models/crm/account'; 2 | import { UsageItem } from '../../models/billing/usage-item'; 3 | import { List } from '../../models/wrappers/list'; 4 | import { ValidationError } from '../../models/wrappers/validation-error'; 5 | import { Request } from '../../util/request'; 6 | import { Store } from '../../util/store'; 7 | import { SubscriptionAddOn } from '../../models/billing/subscription-add-on'; 8 | 9 | export class Usage { 10 | static readonly DEFAULT_FIELDS = [ 11 | '*', 12 | 'Invoice.Uid', 13 | 'SubscriptionAddOn.*', 14 | 'SubscriptionAddOn.Subscription.Uid', 15 | 'SubscriptionAddOn.Subscription.Account.Uid', 16 | 'SubscriptionAddOn.AddOn.Uid' 17 | ].join(','); 18 | private readonly store: Store; 19 | 20 | constructor(store: Store) { 21 | this.store = store; 22 | } 23 | 24 | /** 25 | * Get all usage: 26 | * ```typescript 27 | * const client = new OutsetaApiClient({ 28 | * subdomain: 'test-company', 29 | * apiKey: example_key, 30 | * secretKey: example_secret 31 | * }); 32 | * const response = await client.billing.usage.getAll(); 33 | * console.log(response); 34 | * ``` 35 | * 36 | * Get all usage for a particular account: 37 | * ```typescript 38 | * const client = new OutsetaApiClient({ 39 | * subdomain: 'test-company', 40 | * apiKey: example_key, 41 | * secretKey: example_secret 42 | * }); 43 | * const Account = { 44 | * Uid: 'jW7GJVWq' 45 | * }; 46 | * const response = await client.billing.usage.getAll({ Account }); 47 | * console.log(response); 48 | * ``` 49 | * 50 | * @param options.limit The number of results returned by the API. 51 | * @param options.offset For pagination; returns (limit) results after this value. 52 | * @param options.Account Get all usage for a particular account. 53 | * @param options.fields Not all fields on the model are returned by default - you can request specific fields with a 54 | * that looks something like '*,SubscriptionAddOn.Subscription.Uid'. Note: the shape of the returned object 55 | * may not match the model in this library if this string does not start with '*' as shown. 56 | * @returns The response body if response status OK. 57 | * @throws [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) If the server returns a 58 | * non-"OK" status, the whole response object will be thrown. 59 | */ 60 | public async getAll(options: { 61 | limit?: number, 62 | offset?: number, 63 | fields?: string, 64 | Account?: Required> 65 | } = {}): Promise> { 66 | const request = new Request(this.store, 'billing/usage') 67 | .withParams({ fields: options.fields ? options.fields : Usage.DEFAULT_FIELDS }) 68 | .authenticateAsServer(); 69 | 70 | if (options.limit) request.withParams({ limit: `${options.limit}` }); 71 | if (options.offset) request.withParams({ offset: `${options.offset}` }); 72 | if (options.Account) request.withParams({ 'SubscriptionAddOn.Subscription.Account.Uid': `${options.Account.Uid}` }); 73 | 74 | const response = await request.get(); 75 | 76 | if (response.ok) return await response.json() as List; 77 | else throw response; 78 | } 79 | 80 | /** 81 | * Record usage to an account's subscription. 82 | * 83 | * ```typescript 84 | * const client = new OutsetaApiClient({ 85 | * subdomain: 'test-company', 86 | * apiKey: example_key, 87 | * secretKey: example_secret 88 | * }); 89 | * const response = await client.billing.usage.add({ 90 | * UsageDate: new Date("2021-01-01T08:00:00.000Z"), 91 | * Amount: 10, 92 | * SubscriptionAddOn: { 93 | * Uid: "z9MKvMm4" 94 | * } 95 | * }); 96 | * console.log(response); 97 | * ``` 98 | * 99 | * @param usage The usage to log to Outseta. `SubscriptionAddOn` can be located in the `Subscription` model for an `Account`. 100 | * @param options.fields Not all fields on the model are returned by default - you can request specific fields with a 101 | * that looks something like '*,SubscriptionAddOn.Subscription.Uid'. Note: the shape of the returned object 102 | * may not match the model in this library if this string does not start with '*' as shown. 103 | * @returns The response body if response status OK, or response body with validation errors if response status 400. 104 | * @throws [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) If the server returns a 105 | * non-"OK" status, the whole response object will be thrown. 106 | */ 107 | public async add(usage: UsageAdd, options: { 108 | fields?: string 109 | } = {}): Promise> { 110 | const request = new Request(this.store, 'billing/usage') 111 | .authenticateAsServer() 112 | .withParams({ fields: options.fields ? options.fields : Usage.DEFAULT_FIELDS }) 113 | .withBody(usage); 114 | const response = await request.post(); 115 | 116 | if (response.status === 400) 117 | return await response.json() as ValidationError; 118 | else if (response.ok) 119 | return await response.json() as UsageItem; 120 | else throw response; 121 | } 122 | } 123 | 124 | export interface UsageAdd extends Partial> { 125 | [key: string]: unknown; 126 | Amount: number; 127 | SubscriptionAddOn: Required> & Partial 128 | UsageDate: Date; 129 | } 130 | -------------------------------------------------------------------------------- /src/api/crm/activities.ts: -------------------------------------------------------------------------------- 1 | import { EntityType } from '../../models/shared/entity-type'; 2 | import { Activity } from '../../models/crm/activity'; 3 | import { ActivityType } from '../../models/crm/activity-type'; 4 | import { List } from '../../models/wrappers/list'; 5 | import { ValidationError } from '../../models/wrappers/validation-error'; 6 | import { Request } from '../../util/request'; 7 | import { Store } from '../../util/store'; 8 | 9 | export class Activities { 10 | static readonly DEFAULT_FIELDS = [ 11 | '*' 12 | ].join(','); 13 | private readonly store: Store; 14 | 15 | constructor(store: Store) { 16 | this.store = store; 17 | } 18 | 19 | /** 20 | * Get all activity: 21 | * ```typescript 22 | * const client = new OutsetaApiClient({ 23 | * subdomain: 'test-company', 24 | * apiKey: example_key, 25 | * secretKey: example_secret 26 | * }); 27 | * const response = await client.crm.activities.getAll(); 28 | * console.log(response); 29 | * ``` 30 | * 31 | * Get all activities for a particular account: 32 | * ```typescript 33 | * import { EntityType } from 'outseta-api-client/dist/models/shared/entity-type'; 34 | * 35 | * const client = new OutsetaApiClient({ 36 | * subdomain: 'test-company', 37 | * apiKey: example_key, 38 | * secretKey: example_secret 39 | * }); 40 | * const account = { 41 | * Uid: 'jW7GJVWq' 42 | * }; 43 | * const response = await client.crm.activities.getAll({ 44 | * EntityType: EntityType.Account, 45 | * EntityUid: account.Uid 46 | * }); 47 | * console.log(response); 48 | * ``` 49 | * 50 | * @param options.limit The number of results returned by the API. 51 | * @param options.offset For pagination; returns (limit) results after this value. 52 | * @param options.fields Not all fields on the model are returned by default - you can request specific fields with a 53 | * that looks something like '*,(field name here).(child field name here or * for all)'. Note: the shape of the returned object 54 | * may not match the model in this library if this string does not start with '*' as shown. 55 | * @param options.ActivityType Filter by activity type. 56 | * @param options.EntityType Filter by entity type. 57 | * @param options.EntityUid Filter by uid - should be used in conjunction with the EntityType filter. 58 | * @returns The response body if response status OK. 59 | * @throws [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) If the server returns a 60 | * non-"OK" status, the whole response object will be thrown. 61 | */ 62 | public async getAll(options: { 63 | limit?: number, 64 | offset?: number, 65 | fields?: string, 66 | ActivityType?: ActivityType, 67 | EntityType?: EntityType, 68 | EntityUid?: string 69 | } = {}): Promise> { 70 | const request = new Request(this.store, 'activities') 71 | .withParams({ fields: options.fields ? options.fields : Activities.DEFAULT_FIELDS }) 72 | .authenticateAsServer(); 73 | 74 | if (options.limit) request.withParams({ limit: `${options.limit}` }); 75 | if (options.offset) request.withParams({ offset: `${options.offset}` }); 76 | if (options.ActivityType) request.withParams({ ActivityType: `${options.ActivityType}` }); 77 | if (options.EntityType) request.withParams({ EntityType: `${options.EntityType}` }); 78 | if (options.EntityUid) request.withParams({ EntityUid: `${options.EntityUid}` }); 79 | 80 | const response = await request.get(); 81 | 82 | if (response.ok) return await response.json() as List; 83 | else throw response; 84 | } 85 | 86 | /** 87 | * Record activity to an entity. 88 | * 89 | * ```typescript 90 | * import { EntityType } from 'outseta-api-client/dist/models/shared/entity-type'; 91 | * 92 | * const client = new OutsetaApiClient({ 93 | * subdomain: 'test-company', 94 | * apiKey: example_key, 95 | * secretKey: example_secret 96 | * }); 97 | * const response = await client.crm.activities.add({ 98 | * EntityType: EntityType.Account, 99 | * EntityUid: 'jW7GJVWq', 100 | * Title: 'Example custom activity', 101 | * Description: 'Added a new custom activity' 102 | * }); 103 | * console.log(response); 104 | * ``` 105 | * 106 | * @param activity The activity to log to Outseta. 107 | * @param options.fields Not all fields on the model are returned by default - you can request specific fields with a 108 | * that looks something like '*,(field name here).(child field name here or * for all)'. Note: the shape of the returned object 109 | * may not match the model in this library if this string does not start with '*' as shown. 110 | * @returns The response body if response status OK, or response body with validation errors if response status 400. 111 | * @throws [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) If the server returns a 112 | * non-"OK" status, the whole response object will be thrown. 113 | */ 114 | public async add(activity: ActivityAdd, options: { 115 | fields?: string 116 | } = {}): Promise> { 117 | const request = new Request(this.store, 'activities/customactivity') 118 | .authenticateAsServer() 119 | .withParams({ fields: options.fields ? options.fields : Activities.DEFAULT_FIELDS }) 120 | .withBody(activity); 121 | const response = await request.post(); 122 | 123 | if (response.status === 400) 124 | return await response.json() as ValidationError; 125 | else if (response.ok) 126 | return await response.json() as Activity; 127 | else throw response; 128 | } 129 | } 130 | 131 | export type ActivityAdd = Required> & Partial; 132 | -------------------------------------------------------------------------------- /src/api/crm/index.ts: -------------------------------------------------------------------------------- 1 | import { Store } from '../../util/store'; 2 | import { Activities } from './activities'; 3 | import { Deals } from './deals'; 4 | import { People } from './people'; 5 | import { Accounts } from './accounts'; 6 | 7 | export class Crm { 8 | public readonly accounts: Accounts; 9 | public readonly activities: Activities; 10 | public readonly deals: Deals; 11 | public readonly people: People; 12 | 13 | constructor(store: Store) { 14 | this.accounts = new Accounts(store); 15 | this.activities = new Activities(store); 16 | this.deals = new Deals(store); 17 | this.people = new People(store); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/api/crm/people.ts: -------------------------------------------------------------------------------- 1 | import { Store } from '../../util/store'; 2 | import { Person } from '../../models/crm/person'; 3 | import { Request } from '../../util/request'; 4 | import { List } from '../../models/wrappers/list'; 5 | import { ValidationError } from '../../models/wrappers/validation-error'; 6 | 7 | export class People { 8 | private readonly store: Store; 9 | 10 | constructor(store: Store) { 11 | this.store = store; 12 | } 13 | 14 | /** 15 | * Get all people in the Outseta CRM. 16 | * 17 | * ```typescript 18 | * const client = new OutsetaApiClient({ 19 | * subdomain: 'test-company', 20 | * apiKey: example_key, 21 | * secretKey: example_secret 22 | * }); 23 | * const response = await client.crm.people.getAll(); 24 | * console.log(response); 25 | * ``` 26 | * 27 | * @param options.limit The number of results returned by the API. 28 | * @param options.offset For pagination; returns (limit) results after this value. 29 | * @returns The response body if response status OK. 30 | * @throws [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) If the server returns a 31 | * non-"OK" status, the whole response object will be thrown. 32 | */ 33 | public async getAll(options: { 34 | limit?: number, 35 | offset?: number 36 | } = {}): Promise> { 37 | const request = new Request(this.store, 'crm/people').authenticateAsServer(); 38 | if (options.limit) request.withParams({ limit: `${options.limit}` }); 39 | if (options.offset) request.withParams({ offset: `${options.offset}` }); 40 | 41 | const response = await request.get(); 42 | 43 | if (!response.ok) throw response; 44 | return await response.json() as List; 45 | } 46 | 47 | /** 48 | * Get a specific person in the Outseta CRM by their uid. 49 | * 50 | * ```typescript 51 | * const client = new OutsetaApiClient({ 52 | * subdomain: 'test-company', 53 | * apiKey: example_key, 54 | * secretKey: example_secret 55 | * }); 56 | * const response = await client.crm.people.get(uid); 57 | * console.log(response); 58 | * ``` 59 | * 60 | * @param uid The uid for the person to get. 61 | * @returns The response body if response status OK. 62 | * @throws [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) If the server returns a 63 | * non-"OK" status, the whole response object will be thrown. 64 | */ 65 | public async get(uid: string): Promise { 66 | const request = new Request(this.store, `crm/people/${uid}`).authenticateAsServer(); 67 | const response = await request.get(); 68 | 69 | if (!response.ok) throw response; 70 | return await response.json() as Person; 71 | } 72 | 73 | /** 74 | * Add a person to the CRM. Must include an email address. 75 | * 76 | * ```typescript 77 | * const client = new OutsetaApiClient({ 78 | * subdomain: 'test-company', 79 | * apiKey: example_key, 80 | * secretKey: example_secret 81 | * }); 82 | * const response = await client.crm.people.add({ 83 | * Email: 'hello@tiltcamp.com' 84 | * }); 85 | * console.log(response); 86 | * ``` 87 | * 88 | * @param person The details for the person to add. 89 | * @returns The response body if response status OK, or response body of validation errors if response status 400. 90 | * @throws [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) If the server returns a 91 | * non-"OK" or non-"400" status, the whole response object will be thrown. 92 | */ 93 | public async add(person: PersonAdd): Promise> { 94 | const request = new Request(this.store, 'crm/people') 95 | .authenticateAsServer() 96 | .withBody(person); 97 | const response = await request.post(); 98 | 99 | if (response.status === 400) 100 | return await response.json() as ValidationError; 101 | else if (response.ok) 102 | return await response.json() as Person; 103 | else throw response; 104 | } 105 | 106 | /** 107 | * Update a person in the CRM. Must include their uid. 108 | * 109 | * ```typescript 110 | * const client = new OutsetaApiClient({ 111 | * subdomain: 'test-company', 112 | * apiKey: example_key, 113 | * secretKey: example_secret 114 | * }); 115 | * const response = await client.crm.people.update({ 116 | * Uid: 'DQ2DyknW', 117 | * Email: 'hello@tiltcamp.com' 118 | * }); 119 | * console.log(response); 120 | * ``` 121 | * 122 | * @param person The profile fields and values to update. Must include the user's uid. 123 | * @returns The response body if response status OK, or response body with validation errors if response status 400. 124 | * @throws [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) If the server returns a 125 | * non-"OK" or non-"400" status, the whole response object will be thrown. 126 | */ 127 | public async update(person: PersonUpdate): Promise> { 128 | const request = new Request(this.store, `crm/people/${person.Uid}`) 129 | .authenticateAsServer() 130 | .withBody(person); 131 | const response = await request.put(); 132 | 133 | if (response.status === 400) 134 | return await response.json() as ValidationError; 135 | else if (response.ok) 136 | return await response.json() as Person; 137 | else throw response; 138 | } 139 | 140 | /** 141 | * Delete a specific person in the Outseta CRM by their uid. 142 | * 143 | * ```typescript 144 | * const client = new OutsetaApiClient({ 145 | * subdomain: 'test-company', 146 | * apiKey: example_key, 147 | * secretKey: example_secret 148 | * }); 149 | * const response = await client.crm.people.delete(uid); 150 | * console.log(response); 151 | * ``` 152 | * 153 | * @param uid The uid for the person to delete. 154 | * @returns Null if deletion was successful. 155 | * @throws [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) If the server returns a 156 | * non-"OK" status, the whole response object will be thrown. 157 | */ 158 | public async delete(uid: string): Promise { 159 | const request = new Request(this.store, `crm/people/${uid}`).authenticateAsServer(); 160 | const response = await request.delete(); 161 | 162 | if (!response.ok) throw response; 163 | return null; 164 | } 165 | } 166 | 167 | export interface PersonAdd extends Partial { 168 | [key: string]: unknown; 169 | Email: string; 170 | } 171 | 172 | export interface PersonUpdate extends Partial { 173 | [key: string]: unknown; 174 | Uid: string; 175 | } 176 | -------------------------------------------------------------------------------- /src/api/marketing/email-list-subscriptions.ts: -------------------------------------------------------------------------------- 1 | import { Person } from '../../models/crm/person'; 2 | import { EmailList } from '../../models/marketing/email-list'; 3 | import { EmailListPerson } from '../../models/marketing/email-list-person'; 4 | import { List } from '../../models/wrappers/list'; 5 | import { ValidationError } from '../../models/wrappers/validation-error'; 6 | import { Request } from '../../util/request'; 7 | import { Store } from '../../util/store'; 8 | 9 | export class EmailListSubscriptions { 10 | static readonly DEFAULT_FIELDS = [ 11 | '*' 12 | ].join(','); 13 | private readonly store: Store; 14 | 15 | constructor(store: Store) { 16 | this.store = store; 17 | } 18 | 19 | /** 20 | * Get all subscriptions to a list: 21 | * ```typescript 22 | * const client = new OutsetaApiClient({ 23 | * subdomain: 'test-company', 24 | * apiKey: example_key, 25 | * secretKey: example_secret 26 | * }); 27 | * const response = await client.marketing.emailListSubscriptions.getAll(listUid); 28 | * console.log(response); 29 | * ``` 30 | * 31 | * @param options.limit The number of results returned by the API. 32 | * @param options.offset For pagination; returns (limit) results after this value. 33 | * @param options.fields Not all fields on the model are returned by default - you can request specific fields with a 34 | * that looks something like '*,(field name here).(child field name here or * for all)'. Note: the shape of the 35 | * returned object may not match the model in this library if this string does not start with '*' as shown. 36 | * @returns The response body if response status OK. 37 | * @throws [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) If the server returns a 38 | * non-"OK" status, the whole response object will be thrown. 39 | */ 40 | public async getAll(listUid: string, options: { 41 | limit?: number, 42 | offset?: number, 43 | fields?: string 44 | } = {}): Promise> { 45 | const request = new Request(this.store, `email/lists/${listUid}/subscriptions`) 46 | .withParams({ fields: options.fields ? options.fields : EmailListSubscriptions.DEFAULT_FIELDS }) 47 | // This endpoint throws an exception without an orderBy ¯\_(ツ)_/¯ 48 | .withParams({ orderBy: 'SubscribedDate' }) 49 | .authenticateAsServer(); 50 | if (options.limit) request.withParams({ limit: `${options.limit}` }); 51 | if (options.offset) request.withParams({ offset: `${options.offset}` }); 52 | 53 | const response = await request.get(); 54 | 55 | if (!response.ok) throw response; 56 | return await response.json() as List; 57 | } 58 | 59 | /** 60 | * Add a subscriber to a list with a new person: 61 | * ```typescript 62 | * const client = new OutsetaApiClient({ 63 | * subdomain: 'test-company', 64 | * apiKey: example_key, 65 | * secretKey: example_secret 66 | * }); 67 | * const response = await client.marketing.emailListSubscriptions.add({ 68 | * EmailList: { 69 | * Uid: 'ngWKYnQp' 70 | * }, 71 | * Person: { 72 | * Email: 'hello@tiltcamp.com' 73 | * } 74 | * }); 75 | * console.log(response); 76 | * ``` 77 | * 78 | * Add a subscriber to a list with an existing person: 79 | * ```typescript 80 | * const client = new OutsetaApiClient({ 81 | * subdomain: 'test-company', 82 | * apiKey: example_key, 83 | * secretKey: example_secret 84 | * }); 85 | * const response = await client.marketing.emailListSubscriptions.add({ 86 | * EmailList: { 87 | * Uid: 'ngWKYnQp' 88 | * }, 89 | * Person: { 90 | * Uid: 'DQ2DyknW' 91 | * } 92 | * }); 93 | * console.log(response); 94 | * ``` 95 | * 96 | * @param subscription The subscription to add. 97 | * @param options.fields Not all fields on the model are returned by default - you can request specific fields with a 98 | * that looks something like '*,(field name here).(child field name here or * for all)'. Note: the shape of the 99 | * returned object may not match the model in this library if this string does not start with '*' as shown. 100 | * @returns Null if response status OK, or response body of validation errors if response status 400. 101 | * @throws [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) If the server returns a 102 | * non-"OK" or non-"400" status, the whole response object will be thrown. 103 | */ 104 | public async add(subscription: SubscriptionAdd, options: { 105 | fields?: string 106 | } = {}): Promise> { 107 | const request = new Request(this.store, `email/lists/${subscription.EmailList.Uid}/subscriptions`) 108 | .withParams({ fields: options.fields ? options.fields : EmailListSubscriptions.DEFAULT_FIELDS }) 109 | .authenticateAsServer() 110 | .withBody(subscription); 111 | const response = await request.post(); 112 | 113 | if (response.status === 400) 114 | return await response.json() as ValidationError; 115 | else if (response.ok) 116 | return null; 117 | else throw response; 118 | } 119 | 120 | /** 121 | * Remove a specific subscriber from an email list. 122 | * 123 | * ```typescript 124 | * const client = new OutsetaApiClient({ 125 | * subdomain: 'test-company', 126 | * apiKey: example_key, 127 | * secretKey: example_secret 128 | * }); 129 | * const response = await client.marketing.emailListSubscriptions.delete({ 130 | * EmailList: { 131 | * Uid: 'ngWKYnQp' 132 | * }, 133 | * Person: { 134 | * Uid: 'wQXrBxWK' 135 | * } 136 | * }); 137 | * console.log(response); 138 | * ``` 139 | * 140 | * @param subscription The 'EmailListPerson' object to delete. 141 | * @returns Null if deletion was successful. 142 | * @throws [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) If the server returns a 143 | * non-"OK" status, the whole response object will be thrown. 144 | */ 145 | public async delete(subscription: SubscriptionDelete): Promise { 146 | const request = new Request( 147 | this.store, 148 | `email/lists/${subscription.EmailList.Uid}/subscriptions/${subscription.Person.Uid}` 149 | ).authenticateAsServer(); 150 | const response = await request.delete(); 151 | 152 | if (!response.ok) throw response; 153 | return null; 154 | } 155 | } 156 | 157 | export interface SubscriptionAdd extends Partial> { 158 | [key: string]: unknown; 159 | EmailList: Required>; 160 | Person: Required> | Required>; 161 | } 162 | 163 | export interface SubscriptionDelete extends Partial> { 164 | [key: string]: unknown; 165 | EmailList: Required>; 166 | Person: Required>; 167 | } 168 | -------------------------------------------------------------------------------- /src/api/marketing/index.ts: -------------------------------------------------------------------------------- 1 | import { Store } from '../../util/store'; 2 | import { EmailListSubscriptions } from './email-list-subscriptions'; 3 | 4 | export class Marketing { 5 | public readonly emailListSubscriptions: EmailListSubscriptions; 6 | 7 | constructor(store: Store) { 8 | this.emailListSubscriptions = new EmailListSubscriptions(store); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/api/support/index.ts: -------------------------------------------------------------------------------- 1 | import { Store } from '../../util/store'; 2 | import { Cases } from './cases'; 3 | 4 | export class Support { 5 | public readonly cases: Cases; 6 | 7 | constructor(store: Store) { 8 | this.cases = new Cases(store); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/api/user/index.ts: -------------------------------------------------------------------------------- 1 | import { Store } from '../../util/store'; 2 | import { Request } from '../../util/request'; 3 | import { Profile } from './profile'; 4 | import { Password } from './password'; 5 | 6 | export class User { 7 | public readonly password: Password; 8 | public readonly profile: Profile; 9 | 10 | private readonly store: Store; 11 | 12 | constructor(store: Store) { 13 | this.password = new Password(store); 14 | this.profile = new Profile(store); 15 | 16 | this.store = store; 17 | } 18 | 19 | /** 20 | * Get an access token for a user via their username and password. Even 21 | * if the API client was not initialized with a user access token, 22 | * performing this action will internally set the token if the login 23 | * is successful. 24 | * 25 | * ```typescript 26 | * const client = new OutsetaApiClient({ subdomain: 'test-company' }); 27 | * const response = await client.user.login('username', 'password'); 28 | * console.log(response.access_token); 29 | * ``` 30 | * 31 | * @param username (usually an email address) 32 | * @param password 33 | * @returns The response body with the user's JWT token. 34 | * @throws [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) If the server returns a 35 | * non-"OK" status, the whole response object will be thrown. 36 | */ 37 | public async login(username: string, password: string): Promise { 38 | const request = new Request(this.store, 'tokens').withBody({ 39 | username, 40 | password, 41 | 'grant_type': 'password', 42 | 'client_id': 'outseta_auth_widget' 43 | }); 44 | const response = await request.post(); 45 | if (!response.ok) throw response; 46 | 47 | const responseBody = await response.json() as LoginResponse; 48 | 49 | this.store.userAuth.accessToken = responseBody.access_token; 50 | return responseBody; 51 | } 52 | 53 | /** 54 | * Get an access token for any user using the server's API key and secret. 55 | * Even if the API client was not initialized with a user access token, 56 | * performing this action will internally set the token if the login 57 | * is successful. 58 | * 59 | * ```typescript 60 | * const client = new OutsetaApiClient({ subdomain: 'test-company', apiKey: 'api_key', secretKey: 'api_secret' }); 61 | * const response = await client.user.impersonate('username'); 62 | * console.log(response.access_token); 63 | * ``` 64 | * 65 | * @param username (usually an email address) 66 | * @returns The response body with the user's JWT token. 67 | * @throws [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) If the server returns a 68 | * non-"OK" status, the whole response object will be thrown. 69 | */ 70 | public async impersonate(username: string): Promise { 71 | const request = new Request(this.store, 'tokens') 72 | .authenticateAsServer() 73 | .withBody({ 74 | username, 75 | password: '', 76 | 'grant_type': 'password', 77 | 'client_id': 'outseta_auth_widget' 78 | }); 79 | const response = await request.post(); 80 | if (!response.ok) throw response; 81 | 82 | const responseBody = await response.json() as LoginResponse; 83 | 84 | this.store.userAuth.accessToken = responseBody.access_token; 85 | return responseBody; 86 | } 87 | } 88 | 89 | export interface LoginResponse { 90 | access_token: string; 91 | expires_in: number; 92 | token_type: string; 93 | } 94 | -------------------------------------------------------------------------------- /src/api/user/password.ts: -------------------------------------------------------------------------------- 1 | import { Request } from '../../util/request'; 2 | import { Store } from '../../util/store'; 3 | import { ValidationError } from '../../models/wrappers/validation-error'; 4 | import { Person } from '../../models/crm/person'; 5 | 6 | export class Password { 7 | private readonly store: Store; 8 | 9 | constructor(store: Store) { 10 | this.store = store; 11 | } 12 | 13 | /** 14 | * Change the password belonging to the provided user's access token. 15 | * 16 | * ```typescript 17 | * const client = new OutsetaApiClient({ 18 | * subdomain: 'test-company', 19 | * accessToken: jwt_user_token 20 | * }); 21 | * const response = await client.user.password.update(existingPassword, newPassword); 22 | * console.log(response); 23 | * ``` 24 | * 25 | * @returns Null if response status OK, or the response body with validation errors if response status 400. 26 | * @throws [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) If the server returns a 27 | * non-"OK" or non-"400" status, the whole response object will be thrown. 28 | */ 29 | public async update(existingPassword: string, newPassword: string): Promise> { 30 | const request = new Request(this.store, 'profile/password') 31 | .authenticateAsUser() 32 | .withBody({ 33 | ExistingPassword: existingPassword, 34 | NewPassword: newPassword 35 | }); 36 | const response = await request.put(); 37 | 38 | if (response.status === 400) 39 | return await response.json() as ValidationError; 40 | else if (response.ok) 41 | return null; 42 | else throw response; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/api/user/profile.ts: -------------------------------------------------------------------------------- 1 | import { Request } from '../../util/request'; 2 | import { Store } from '../../util/store'; 3 | import { Person } from '../../models/crm/person'; 4 | import { ValidationError } from '../../models/wrappers/validation-error'; 5 | 6 | export class Profile { 7 | private readonly store: Store; 8 | 9 | constructor(store: Store) { 10 | this.store = store; 11 | } 12 | 13 | /** 14 | * Get the profile belonging to the provided user's access token. 15 | * 16 | * ```typescript 17 | * const client = new OutsetaApiClient({ 18 | * subdomain: 'test-company', 19 | * accessToken: jwt_user_token 20 | * }); 21 | * const response = await client.user.profile.get(); 22 | * console.log(response); 23 | * ``` 24 | * 25 | * @param options.fields Not all fields on the model are returned by default - you can request specific fields with a 26 | * string that looks something like '*,Account.*'. Note: the shape of the returned object may not match the model in 27 | * this library if this string does not start with '*' as shown. 28 | * @returns The response body. 29 | * @throws [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) If the server returns a 30 | * non-"OK" status, the whole response object will be thrown. 31 | */ 32 | public async get(options: { 33 | fields?: string 34 | } = {}): Promise { 35 | const request = new Request(this.store, 'profile').authenticateAsUser(); 36 | if (options.fields) request.withParams({ fields: options.fields }); 37 | 38 | const response = await request.get(); 39 | 40 | if (!response.ok) throw response; 41 | return await response.json() as Person; 42 | } 43 | 44 | /** 45 | * Update the profile belonging to the provided user's uid and access token. 46 | * 47 | * ```typescript 48 | * const client = new OutsetaApiClient({ 49 | * subdomain: 'test-company', 50 | * accessToken: jwt_user_token 51 | * }); 52 | * const response = await client.user.profile.update({ 53 | * Uid: 'DQ2DyknW', 54 | * FirstName: 'Jane', 55 | * LastName: 'Doe' 56 | * }); 57 | * console.log(response); 58 | * ``` 59 | * 60 | * @param profile The profile fields and values to update. Must include the user's uid. 61 | * @returns The response body if response status OK, or response body with validation errors if response status 400. 62 | * @throws [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) If the server returns a 63 | * non-"OK" or non-"400" status, the whole response object will be thrown. 64 | */ 65 | public async update(profile: ProfileUpdate): Promise> { 66 | const request = new Request(this.store, 'profile') 67 | .authenticateAsUser() 68 | .withBody(profile); 69 | const response = await request.put(); 70 | 71 | if (response.status === 400) 72 | return await response.json() as ValidationError; 73 | else if (response.ok) 74 | return await response.json() as Person; 75 | else throw response; 76 | } 77 | } 78 | 79 | export interface ProfileUpdate extends Partial { 80 | [key: string]: unknown; 81 | Uid: string; 82 | } 83 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { Marketing } from './api/marketing'; 2 | import { Support } from './api/support'; 3 | import { ServerCredentials, UserCredentials } from './util/credentials'; 4 | import { Store } from './util/store'; 5 | import { User } from './api/user'; 6 | import { Billing } from './api/billing'; 7 | import { Crm } from './api/crm'; 8 | 9 | // eslint-disable-next-line import/no-default-export 10 | export default class OutsetaApiClient { 11 | public readonly billing: Billing; 12 | public readonly crm: Crm; 13 | public readonly marketing: Marketing; 14 | public readonly support: Support; 15 | public readonly user: User; 16 | 17 | /** 18 | * Initializing without any keys: 19 | * ```typescript 20 | * import OutsetaApiClient from 'outseta-api-client'; 21 | * const client = new OutsetaApiClient({subdomain: 'test-company'}); 22 | * ``` 23 | * 24 | * Initializing with server-side API keys: 25 | * ```typescript 26 | * import OutsetaApiClient from 'outseta-api-client'; 27 | * const client = new OutsetaApiClient({ 28 | * subdomain: 'test-company', 29 | * apiKey: example_key, 30 | * secretKey: example_secret 31 | * }); 32 | * ``` 33 | * 34 | * Initializing with a user access token: 35 | * ```typescript 36 | * import OutsetaApiClient from 'outseta-api-client'; 37 | * const client = new OutsetaApiClient({ 38 | * subdomain: 'test-company', 39 | * accessToken: jwt_user_token 40 | * }); 41 | * ``` 42 | * @param subdomain If your Outseta domain is `tiltcamp.outseta.com`, this would be `tiltcamp` 43 | * @param accessToken A user's access token (if available) 44 | * @param apiKey A server-side API key (if available) 45 | * @param secretKey A server-side API key secret (if available) 46 | */ 47 | constructor({ subdomain, accessToken, apiKey, secretKey }: { 48 | subdomain: string; 49 | accessToken?: string; 50 | apiKey?: string; 51 | secretKey?: string; 52 | }) { 53 | const baseUrl = `https://${subdomain}.outseta.com/api/v1/`; 54 | const userAuth = new UserCredentials(accessToken); 55 | const serverAuth = new ServerCredentials(apiKey, secretKey); 56 | const store = new Store(baseUrl, userAuth, serverAuth); 57 | 58 | this.billing = new Billing(store); 59 | this.crm = new Crm(store); 60 | this.marketing = new Marketing(store); 61 | this.support = new Support(store); 62 | this.user = new User(store); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/models/billing/add-on.ts: -------------------------------------------------------------------------------- 1 | import { BillingAddOnType } from './billing-add-on-type'; 2 | import { PlanAddOn } from './plan-add-on'; 3 | 4 | export interface AddOn { 5 | Name: string; 6 | BillingAddOnType: BillingAddOnType; 7 | IsQuantityEditable: boolean; 8 | MinimumQuantity: number; 9 | MonthlyRate: number; 10 | AnnualRate: number; 11 | SetupFee: number; 12 | UnitOfMeasure: string; 13 | IsTaxable: boolean; 14 | IsBilledDuringTrial: boolean; 15 | PlanAddOns?: PlanAddOn[]; 16 | Uid: string; 17 | Created: Date; 18 | Updated: Date; 19 | } 20 | -------------------------------------------------------------------------------- /src/models/billing/billing-add-on-type.ts: -------------------------------------------------------------------------------- 1 | export enum BillingAddOnType { 2 | Fixed = 1, 3 | Usage, 4 | OneTime 5 | } 6 | -------------------------------------------------------------------------------- /src/models/billing/billing-renewal-term.ts: -------------------------------------------------------------------------------- 1 | export enum BillingRenewalTerm { 2 | Monthly = 1, 3 | Annually 4 | } 5 | -------------------------------------------------------------------------------- /src/models/billing/billing-transaction-type.ts: -------------------------------------------------------------------------------- 1 | export enum BillingTransactionType { 2 | Invoice = 1, 3 | Payment, 4 | Credit, 5 | Refund, 6 | Chargeback 7 | } 8 | -------------------------------------------------------------------------------- /src/models/billing/charge-summary.ts: -------------------------------------------------------------------------------- 1 | import { InvoiceDisplayItem } from './invoice-display-item'; 2 | 3 | export interface ChargeSummary { 4 | Number: number; 5 | InvoiceDate: Date; 6 | Subtotal: number; 7 | Tax: number; 8 | Paid: number; 9 | InvoiceDisplayItems: InvoiceDisplayItem[]; 10 | Total: number; 11 | Balance: number; 12 | } 13 | -------------------------------------------------------------------------------- /src/models/billing/invoice-display-item.ts: -------------------------------------------------------------------------------- 1 | export interface InvoiceDisplayItem { 2 | Date: Date; 3 | Type: string; 4 | Description: string; 5 | Amount: number; 6 | Tax: number; 7 | Total: number; 8 | } 9 | -------------------------------------------------------------------------------- /src/models/billing/invoice-line-item.ts: -------------------------------------------------------------------------------- 1 | import { EntityType } from '../shared/entity-type'; 2 | import { Invoice } from './invoice'; 3 | 4 | export interface InvoiceLineItem { 5 | StartDate?: Date; 6 | EndDate?: Date; 7 | Description: string; 8 | UnitOfMeasure: string; 9 | Quantity?: number; 10 | Rate: number; 11 | Amount: number; 12 | Tax: number; 13 | Invoice?: Invoice; 14 | LineItemType: EntityType; 15 | LineItemEntityUid?: string; 16 | Uid: string; 17 | Created: Date; 18 | Updated: Date; 19 | } 20 | -------------------------------------------------------------------------------- /src/models/billing/invoice.ts: -------------------------------------------------------------------------------- 1 | import { Subscription } from './subscription'; 2 | import { InvoiceLineItem } from './invoice-line-item'; 3 | 4 | export interface Invoice { 5 | InvoiceDate: Date; 6 | PaymentReminderSentDate?: Date; 7 | Number: number; 8 | BillingInvoiceStatus: number; 9 | Subscription: Subscription; 10 | Amount: number; 11 | AmountOutstanding: number; 12 | InvoiceLineItems: InvoiceLineItem[]; 13 | IsUserGenerated: boolean; 14 | Uid: string; 15 | Created: Date; 16 | Updated: Date; 17 | } 18 | -------------------------------------------------------------------------------- /src/models/billing/plan-add-on.ts: -------------------------------------------------------------------------------- 1 | export interface PlanAddOn { 2 | IsUserSelectable: boolean; 3 | Uid: string; 4 | Created: Date; 5 | Updated: Date; 6 | } 7 | -------------------------------------------------------------------------------- /src/models/billing/plan-family.ts: -------------------------------------------------------------------------------- 1 | import { Plan } from './plan'; 2 | 3 | export interface PlanFamily { 4 | Name: string; 5 | IsActive: boolean; 6 | Plans?: Plan[]; 7 | Uid: string; 8 | Created: Date; 9 | Updated: Date; 10 | } 11 | -------------------------------------------------------------------------------- /src/models/billing/plan.ts: -------------------------------------------------------------------------------- 1 | import { PlanAddOn } from './plan-add-on'; 2 | import { PlanFamily } from './plan-family'; 3 | 4 | export interface Plan { 5 | Name: string; 6 | Description: string; 7 | PlanFamily: PlanFamily; 8 | IsQuantityEditable: boolean; 9 | MinimumQuantity: number; 10 | MonthlyRate: number; 11 | AnnualRate: number; 12 | SetupFee: number; 13 | IsTaxable: boolean; 14 | IsActive: boolean; 15 | TrialPeriodDays: number; 16 | UnitOfMeasure: string; 17 | PlanAddOns: PlanAddOn[]; 18 | NumberOfSubscriptions: number; 19 | Uid: string; 20 | Created: Date; 21 | Updated: Date; 22 | } 23 | -------------------------------------------------------------------------------- /src/models/billing/subscription-add-on.ts: -------------------------------------------------------------------------------- 1 | import { AddOn } from './add-on'; 2 | import { Subscription } from './subscription'; 3 | 4 | export interface SubscriptionAddOn { 5 | BillingRenewalTerm: number; 6 | Subscription: Partial; 7 | AddOn: Partial; 8 | Quantity?: unknown; 9 | StartDate: Date; 10 | EndDate: Date; 11 | RenewalDate?: Date; 12 | NewRequiredQuantity?: unknown; 13 | Uid: string; 14 | Created: Date; 15 | Updated: Date; 16 | } 17 | -------------------------------------------------------------------------------- /src/models/billing/subscription.ts: -------------------------------------------------------------------------------- 1 | import { Account } from '../crm/account'; 2 | import { Plan } from './plan'; 3 | import { BillingRenewalTerm } from './billing-renewal-term'; 4 | import { SubscriptionAddOn } from './subscription-add-on'; 5 | 6 | export interface Subscription { 7 | Uid?: string; 8 | Plan: Plan; 9 | BillingRenewalTerm: BillingRenewalTerm; 10 | Account: Partial; 11 | SubscriptionAddOns?: SubscriptionAddOn[]; 12 | Quantity?: unknown; 13 | StartDate?: Date; 14 | EndDate?: Date; 15 | RenewalDate?: Date; 16 | NewRequiredQuantity?: unknown; 17 | IsPlanUpgradeRequired?: boolean; 18 | PlanUpgradeRequiredMessage?: string; 19 | DiscountCouponSubscriptions?: unknown[]; 20 | Created?: Date; 21 | Updated?: Date; 22 | } 23 | -------------------------------------------------------------------------------- /src/models/billing/transaction.ts: -------------------------------------------------------------------------------- 1 | import { Account } from "../crm/account"; 2 | import { BillingTransactionType } from "./billing-transaction-type"; 3 | import { Invoice } from "./invoice"; 4 | 5 | export interface Transaction { 6 | TransactionDate: Date; 7 | BillingTransactionType: BillingTransactionType; 8 | Account: Account; 9 | Invoice: Invoice; 10 | Amount: number; 11 | IsElectronicTransaction: boolean; 12 | Uid: string; 13 | Created: Date; 14 | Updated: Date; 15 | } 16 | -------------------------------------------------------------------------------- /src/models/billing/usage-item.ts: -------------------------------------------------------------------------------- 1 | import { Invoice } from "./invoice"; 2 | import { SubscriptionAddOn } from "./subscription-add-on"; 3 | 4 | export interface UsageItem { 5 | UsageDate: Date; 6 | Invoice?: Invoice; 7 | SubscriptionAddOn: SubscriptionAddOn; 8 | Amount: number; 9 | Uid: string; 10 | Created: Date; 11 | Updated: Date; 12 | } 13 | -------------------------------------------------------------------------------- /src/models/crm/account-stage.ts: -------------------------------------------------------------------------------- 1 | export enum AccountStage { 2 | Trialing = 2, 3 | Subscribing , 4 | Cancelling , 5 | Expired, 6 | TrialExpired, 7 | PastDue 8 | } 9 | -------------------------------------------------------------------------------- /src/models/crm/account.ts: -------------------------------------------------------------------------------- 1 | import { Address } from '../shared/address'; 2 | import { Deal } from './deal'; 3 | import { PersonAccount } from './person-account'; 4 | import { AccountStage } from './account-stage'; 5 | import { Subscription } from '../billing/subscription'; 6 | 7 | export interface Account { 8 | Name: string; 9 | ClientIdentifier?: unknown; 10 | IsDemo: boolean; 11 | BillingAddress: Address; 12 | MailingAddress: Address; 13 | AccountStage: AccountStage; 14 | PaymentInformation?: unknown; 15 | PersonAccount: PersonAccount[]; 16 | Subscriptions: Subscription[]; 17 | Deals: Deal[]; 18 | LastLoginDateTime?: unknown; 19 | AccountSpecificPageUrl1: string; 20 | AccountSpecificPageUrl2: string; 21 | AccountSpecificPageUrl3: string; 22 | AccountSpecificPageUrl4: string; 23 | AccountSpecificPageUrl5: string; 24 | RewardFulReferralId?: unknown; 25 | HasLoggedIn: boolean; 26 | AccountStageLabel: string; 27 | DomainName?: unknown; 28 | LatestSubscription?: unknown; 29 | CurrentSubscription?: unknown; 30 | PrimaryContact?: unknown; 31 | PrimarySubscription?: unknown; 32 | RecaptchaToken?: unknown; 33 | LifetimeRevenue: number; 34 | Uid: string; 35 | Created: Date; 36 | Updated: Date; 37 | } 38 | -------------------------------------------------------------------------------- /src/models/crm/activity-type.ts: -------------------------------------------------------------------------------- 1 | export enum ActivityType { 2 | Custom = 10, 3 | Note = 50, 4 | Email = 51, 5 | PhoneCall = 52, 6 | Meeting = 53, 7 | AccountCreated = 100, 8 | AccountUpdated = 101, 9 | AccountAddPerson = 102, 10 | AccountStageUpdated = 103, 11 | AccountDeleted = 104, 12 | AccountBillingInformationUpdated = 105, 13 | PersonCreated = 200, 14 | PersonUpdated = 201, 15 | PersonDeleted = 202, 16 | PersonLogin = 203, 17 | PersonListSubscribed = 204, 18 | PersonListUnsubscribed = 205, 19 | PersonSegmentAdded = 206, 20 | PersonSegmentRemoved = 207, 21 | PersonEmailOpened = 208, 22 | PersonEmailClicked = 209, 23 | PersonEmailBounce = 210, 24 | PersonEmailSpan = 211, 25 | PersonSupportTicketCreated = 212, 26 | PersonSupportTicketUpdated = 213, 27 | DealCreated = 300, 28 | DealUpdated = 301, 29 | DealAddPerson = 302, 30 | DealAddAccount = 303 31 | } 32 | -------------------------------------------------------------------------------- /src/models/crm/activity.ts: -------------------------------------------------------------------------------- 1 | import { ActivityType } from "./activity-type"; 2 | import { EntityType } from "../shared/entity-type"; 3 | 4 | export interface Activity { 5 | Title: string; 6 | Description: string; 7 | ActivityData?: string; 8 | ActivityDateTime: Date; 9 | ActivityType: ActivityType; 10 | EntityType: EntityType; 11 | EntityUid: string; 12 | Uid: string; 13 | Created: Date; 14 | Updated: Date; 15 | } 16 | -------------------------------------------------------------------------------- /src/models/crm/deal-person.ts: -------------------------------------------------------------------------------- 1 | import { Deal } from './deal'; 2 | import { Person } from "./person"; 3 | 4 | export interface DealPerson { 5 | Person?: Person; 6 | Deal?: Deal; 7 | Uid: string; 8 | Created: Date; 9 | Updated: Date; 10 | } 11 | -------------------------------------------------------------------------------- /src/models/crm/deal-pipeline-stage.ts: -------------------------------------------------------------------------------- 1 | import { Deal } from './deal'; 2 | import { DealPipeline } from './deal-pipeline'; 3 | 4 | export interface DealPipelineStage { 5 | Weight: number; 6 | Name: string; 7 | DealPipeline?: DealPipeline[]; 8 | Deals?: Deal[]; 9 | Uid: string; 10 | Created: Date; 11 | Updated: Date; 12 | } 13 | -------------------------------------------------------------------------------- /src/models/crm/deal-pipeline.ts: -------------------------------------------------------------------------------- 1 | import { DealPipelineStage } from './deal-pipeline-stage'; 2 | 3 | export interface DealPipeline { 4 | Name: string; 5 | DealPipelineStages?: DealPipelineStage[]; 6 | Uid: string; 7 | Created: Date; 8 | Updated: Date; 9 | } 10 | -------------------------------------------------------------------------------- /src/models/crm/deal.ts: -------------------------------------------------------------------------------- 1 | import { Account } from './account'; 2 | import { DealPerson } from './deal-person'; 3 | import { DealPipelineStage } from './deal-pipeline-stage'; 4 | import { Person } from './person'; 5 | 6 | export interface Deal { 7 | Name: string; 8 | Amount: number; 9 | DueDate: Date; 10 | AssignedToPersonClientIdentifier: string; 11 | Weight: number; 12 | DealPipelineStage?: DealPipelineStage; 13 | Account?: Partial 14 | DealPeople?: DealPerson[]; 15 | Contacts: string; 16 | Owner?: Person; 17 | Uid: string; 18 | Created: Date; 19 | Updated: Date; 20 | } 21 | -------------------------------------------------------------------------------- /src/models/crm/person-account.ts: -------------------------------------------------------------------------------- 1 | import { Person } from './person'; 2 | import { Account } from './account'; 3 | 4 | export interface PersonAccount { 5 | Person?: Person; 6 | Account?: Account; 7 | IsPrimary: boolean; 8 | Uid?: string; 9 | Created?: Date; 10 | Updated?: Date; 11 | } 12 | -------------------------------------------------------------------------------- /src/models/crm/person.ts: -------------------------------------------------------------------------------- 1 | import { Address } from '../shared/address'; 2 | import { Account } from './account'; 3 | import { DealPerson } from './deal-person'; 4 | import { PersonAccount } from './person-account'; 5 | 6 | export interface Person { 7 | Email: string; 8 | FirstName?: string; 9 | LastName?: string; 10 | ProfileImageS3Url?: string; 11 | MailingAddress?: Address; 12 | PasswordMustChange?: boolean; 13 | PhoneMobile?: string; 14 | PhoneWork?: string; 15 | Title?: string; 16 | Timezone?: unknown; 17 | Language?: string; 18 | IPAddress?: string; 19 | Referer?: string; 20 | UserAgent?: string; 21 | LastLoginDateTime?: Date; 22 | OAuthGoogleProfileId?: unknown; 23 | PersonAccount?: PersonAccount[]; 24 | DealPeople?: DealPerson[]; 25 | Account?: Account; 26 | FullName?: string; 27 | OAuthIntegrationStatus?: number; 28 | UserAgentPlatformBrowser?: string; 29 | Uid?: string; 30 | Created?: Date; 31 | Updated?: Date; 32 | } 33 | -------------------------------------------------------------------------------- /src/models/marketing/email-list-person.ts: -------------------------------------------------------------------------------- 1 | import { Person } from '../crm/person'; 2 | import { EmailList } from "./email-list"; 3 | 4 | export interface EmailListPerson { 5 | EmailList: EmailList; 6 | Person: Person; 7 | EmailListSubscriberStatus: number; 8 | SubscribedDate: Date; 9 | ConfirmedDate?: Date; 10 | UnsubscribedDate?: Date; 11 | CleanedDate?: Date; 12 | WelcomeEmailDeliverDateTime?: Date; 13 | WelcomeEmailOpenDateTime?: Date; 14 | UnsubscribeReason?: string; 15 | UnsubscribeReasonOther?: string; 16 | RecaptchaToken?: string; 17 | RecaptchaSiteKey?: string; 18 | SendWelcomeEmail: boolean; 19 | Source?: string; 20 | Uid: string; 21 | Created: Date; 22 | Updated: Date; 23 | } 24 | -------------------------------------------------------------------------------- /src/models/marketing/email-list.ts: -------------------------------------------------------------------------------- 1 | import { EmailListPerson } from "./email-list-person"; 2 | 3 | export interface EmailList { 4 | Name: string; 5 | WelcomeSubject?: string; 6 | WelcomeBody: string; 7 | WelcomeFromName?: string; 8 | WelcomeFromEmail?: string; 9 | EmailListPerson: EmailListPerson[]; 10 | CountSubscriptionsActive: number; 11 | CountSubscriptionsBounce: number; 12 | CountSubscriptionsSpam: number; 13 | CountSubscriptionsUnsubscribed: number; 14 | Uid: string; 15 | Created: Date; 16 | Updated: Date; 17 | } 18 | -------------------------------------------------------------------------------- /src/models/shared/address.ts: -------------------------------------------------------------------------------- 1 | export interface Address { 2 | AddressLine1?: string; 3 | AddressLine2?: string; 4 | AddressLine3?: string; 5 | City: string; 6 | State: string; 7 | PostalCode: string; 8 | Country?: string; 9 | Uid?: string; 10 | Created?: Date; 11 | Updated?: Date; 12 | } 13 | -------------------------------------------------------------------------------- /src/models/shared/entity-type.ts: -------------------------------------------------------------------------------- 1 | export enum EntityType { 2 | Account = 1, 3 | Person, 4 | Deal 5 | } 6 | -------------------------------------------------------------------------------- /src/models/support/case-history.ts: -------------------------------------------------------------------------------- 1 | import { Case } from './case'; 2 | 3 | export interface CaseHistory { 4 | HistoryDateTime: Date; 5 | Case?: Case; 6 | AgentName: string; 7 | Comment: string; 8 | Type: number; 9 | SeenDateTime?: Date; 10 | ClickDateTime?: Date; 11 | PersonEmail?: unknown; 12 | NewUvi?: unknown; 13 | Uid: string; 14 | Created: Date; 15 | Updated: Date; 16 | } 17 | -------------------------------------------------------------------------------- /src/models/support/case-source.ts: -------------------------------------------------------------------------------- 1 | export enum CaseSource { 2 | Website = 1, 3 | Email, 4 | Facebook, 5 | Twitter 6 | } 7 | -------------------------------------------------------------------------------- /src/models/support/case-status.ts: -------------------------------------------------------------------------------- 1 | export enum CaseStatus { 2 | Open = 1, 3 | Closed 4 | } 5 | -------------------------------------------------------------------------------- /src/models/support/case.ts: -------------------------------------------------------------------------------- 1 | import { Person } from '../crm/person'; 2 | import { CaseHistory } from './case-history'; 3 | import { CaseSource } from './case-source'; 4 | import { CaseStatus } from './case-status'; 5 | 6 | export interface Case { 7 | SubmittedDateTime: Date; 8 | FromPerson?: Person; 9 | AssignedToPersonClientIdentifier: string; 10 | Subject: string; 11 | Body: string; 12 | UserAgent?: string; 13 | Status: CaseStatus; 14 | Source: CaseSource; 15 | CaseHistories?: CaseHistory[]; 16 | IsOnline: boolean; 17 | LastCaseHistory?: CaseHistory; 18 | Participants?: string; 19 | RecaptchaToken?: string; 20 | Uid: string; 21 | Created: Date; 22 | Updated: Date; 23 | } 24 | -------------------------------------------------------------------------------- /src/models/wrappers/list.ts: -------------------------------------------------------------------------------- 1 | export interface List { 2 | metadata: Metadata; 3 | items: T[]; 4 | } 5 | 6 | export interface Metadata { 7 | limit: number; 8 | offset: number; 9 | total: number; 10 | } 11 | -------------------------------------------------------------------------------- /src/models/wrappers/validation-error.ts: -------------------------------------------------------------------------------- 1 | export interface ValidationError { 2 | ErrorMessage: string; 3 | EntityValidationErrors: EntityValidationError[]; 4 | } 5 | 6 | export interface EntityValidationError { 7 | Entity: T; 8 | TypeName: string; 9 | ValidationErrors: ErrorDetail[]; 10 | } 11 | 12 | export interface ErrorDetail { 13 | ErrorCode: string; 14 | ErrorMessage: string; 15 | PropertyName: string; 16 | } 17 | -------------------------------------------------------------------------------- /src/util/credentials.ts: -------------------------------------------------------------------------------- 1 | interface Credentials { 2 | authorizationHeader: string; 3 | isReady: boolean; 4 | } 5 | 6 | export class ServerCredentials implements Credentials { 7 | apiKey?: string; 8 | secretKey?: string; 9 | 10 | constructor(apiKey?: string, secretKey?: string) { 11 | this.apiKey = apiKey; 12 | this.secretKey = secretKey; 13 | } 14 | 15 | get authorizationHeader(): string { 16 | if (!this.isReady) 17 | throw 'The API client was not initialized with API keys.'; 18 | 19 | return `Outseta ${this.apiKey}:${this.secretKey}`; 20 | } 21 | 22 | get isReady(): boolean { 23 | return !!(this.apiKey && this.secretKey); 24 | } 25 | } 26 | 27 | export class UserCredentials implements Credentials { 28 | public accessToken?: string; 29 | 30 | constructor(accessToken?: string) { 31 | this.accessToken = accessToken; 32 | } 33 | 34 | get authorizationHeader(): string { 35 | if (!this.isReady) 36 | throw 'The API client doesn\'t have a user token. Please initialize the client with one or ' + 37 | 'call profile.login() first.'; 38 | 39 | return `bearer ${this.accessToken}`; 40 | } 41 | 42 | get isReady(): boolean { 43 | return !!this.accessToken; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/util/request.ts: -------------------------------------------------------------------------------- 1 | import { Store } from './store'; 2 | 3 | export class Request { 4 | private readonly store: Store; 5 | private _options: Options = { 6 | headers: { 7 | 'Content-Type': 'application/json' 8 | } 9 | }; 10 | private url: URL; 11 | 12 | constructor(store: Store, endpoint: string) { 13 | this.store = store; 14 | while (endpoint.startsWith('/')) endpoint = endpoint.substring(1); 15 | this.url = new URL(`${store.baseUrl}${endpoint}`); 16 | } 17 | 18 | authenticateAsUser(): this { 19 | this._options.headers['Authorization'] = this.store.userAuth.authorizationHeader; 20 | return this; 21 | } 22 | 23 | authenticateAsUserPreferred(): this { 24 | if (this.store.userAuth.isReady) this.authenticateAsUser(); 25 | else if (this.store.serverAuth.isReady) this.authenticateAsServer(); 26 | 27 | return this; 28 | } 29 | 30 | authenticateAsServer(): this { 31 | this._options.headers['Authorization'] = this.store.serverAuth.authorizationHeader; 32 | return this; 33 | } 34 | 35 | authenticateAsServerPreferred(): this { 36 | if (this.store.serverAuth.isReady) this.authenticateAsServer(); 37 | else if (this.store.userAuth.isReady) this.authenticateAsUser(); 38 | 39 | return this; 40 | } 41 | 42 | withParams(params: { [key: string]: string }): this { 43 | Object.keys(params).forEach(key => this.url.searchParams.append(key, params[key])); 44 | return this; 45 | } 46 | 47 | withBody(body: Record): this { 48 | if (this._options.body) { 49 | const existingBody = JSON.parse(this._options.body); 50 | body = Object.assign(existingBody, body); 51 | } 52 | 53 | this._options.body = JSON.stringify(body); 54 | return this; 55 | } 56 | 57 | get(): Promise { 58 | return this.execute('GET'); 59 | } 60 | 61 | post(): Promise { 62 | return this.execute('POST'); 63 | } 64 | 65 | put(): Promise { 66 | return this.execute('PUT'); 67 | } 68 | 69 | patch(): Promise { 70 | return this.execute('PATCH'); 71 | } 72 | 73 | delete(): Promise { 74 | return this.execute('DELETE'); 75 | } 76 | 77 | private execute(method: Method): Promise { 78 | this._options.method = method; 79 | return fetch(this.url.toString(), this._options); 80 | } 81 | } 82 | 83 | type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'HEAD' | 'OPTIONS' | 'DELETE'; 84 | 85 | interface Options { 86 | method?: Method; 87 | headers: { [key: string]: string }; 88 | credentials?: 'include'; 89 | body?: string; 90 | } 91 | -------------------------------------------------------------------------------- /src/util/store.ts: -------------------------------------------------------------------------------- 1 | import { ServerCredentials, UserCredentials } from './credentials'; 2 | 3 | export class Store { 4 | public readonly baseUrl: string; 5 | public readonly userAuth: UserCredentials; 6 | public readonly serverAuth: ServerCredentials; 7 | 8 | constructor(baseUrl: string, userAuth: UserCredentials, serverAuth: ServerCredentials) { 9 | this.baseUrl = baseUrl; 10 | this.userAuth = userAuth; 11 | this.serverAuth = serverAuth; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/api/billing/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { Billing } from '../../../src/api/billing'; 2 | import { Plans } from '../../../src/api/billing/plans'; 3 | import { Transactions } from '../../../src/api/billing/transactions'; 4 | import { Usage } from '../../../src/api/billing/usage'; 5 | import { Store } from '../../../src/util/store'; 6 | import { ServerCredentials, UserCredentials } from '../../../src/util/credentials'; 7 | import { Invoices } from '../../../src/api/billing/invoices'; 8 | import { PlanFamilies } from '../../../src/api/billing/plan-families'; 9 | 10 | describe('api', () => 11 | describe('Billing', () => 12 | describe('constructor', () => { 13 | it('creates successfully', () => { 14 | const billing = new Billing( 15 | new Store( 16 | 'https://test-company.outseta.com/api/', 17 | new UserCredentials(), 18 | new ServerCredentials() 19 | ) 20 | ); 21 | 22 | expect(billing).toBeInstanceOf(Billing); 23 | expect(billing.invoices).toBeInstanceOf(Invoices); 24 | expect(billing.plans).toBeInstanceOf(Plans); 25 | expect(billing.planFamilies).toBeInstanceOf(PlanFamilies); 26 | expect(billing.transactions).toBeInstanceOf(Transactions); 27 | expect(billing.usage).toBeInstanceOf(Usage); 28 | }); 29 | }) 30 | ) 31 | ); 32 | -------------------------------------------------------------------------------- /test/api/billing/plan-families/get-all.spec.ts: -------------------------------------------------------------------------------- 1 | import Pretender, { ResponseHandler } from 'pretender'; 2 | 3 | import { Store } from '../../../../src/util/store'; 4 | import { ServerCredentials, UserCredentials } from '../../../../src/util/credentials'; 5 | import { PlanFamilies } from '../../../../src/api/billing/plan-families'; 6 | 7 | describe('api', () => { 8 | describe('Billing', () => { 9 | describe('PlanFamilies', () => { 10 | describe('getAll', () => { 11 | let server: Pretender; 12 | let store: Store; 13 | 14 | let planFamilies: PlanFamilies; 15 | 16 | beforeEach(() => { 17 | if (server) server.shutdown(); 18 | 19 | store = new Store( 20 | 'https://test-company.outseta.com/api/v1/', 21 | new UserCredentials(), 22 | new ServerCredentials('example_key', 'example_secret') 23 | ); 24 | 25 | planFamilies = new PlanFamilies(store); 26 | }); 27 | 28 | afterAll(() => { 29 | server.shutdown(); 30 | }); 31 | 32 | it('handles successful request', async () => { 33 | const responseHandler: ResponseHandler = (request) => { 34 | expect(request.requestHeaders['authorization']).toBeUndefined(); 35 | expect(request.queryParams).toEqual({}); 36 | expect(request.requestBody).toBeNull(); 37 | expect(request.requestHeaders['content-type']).toBe('application/json'); 38 | 39 | return [ 40 | 200, 41 | {'Content-Type': 'application/json'}, 42 | JSON.stringify(exampleResponse) 43 | ]; 44 | }; 45 | server = new Pretender(function () { 46 | this.get('https://test-company.outseta.com/api/v1/billing/planfamilies', responseHandler); 47 | }); 48 | 49 | const response = await planFamilies.getAll(); 50 | expect(response.metadata.total).toBe(1); 51 | expect(response.items).toHaveSize(1); 52 | expect(response.items[0].Name).toBe('Main Plans'); 53 | }); 54 | 55 | it('handles request with pagination', async () => { 56 | const responseHandler: ResponseHandler = (request) => { 57 | expect(request.requestHeaders['authorization']).toBeUndefined(); 58 | expect(request.queryParams).toEqual({ offset: '10', limit: '20' }); 59 | expect(request.requestBody).toBeNull(); 60 | expect(request.requestHeaders['content-type']).toBe('application/json'); 61 | 62 | return [ 63 | 200, 64 | {'Content-Type': 'application/json'}, 65 | JSON.stringify(exampleNoResults) 66 | ]; 67 | }; 68 | server = new Pretender(function () { 69 | this.get('https://test-company.outseta.com/api/v1/billing/planfamilies', responseHandler); 70 | }); 71 | 72 | await planFamilies.getAll({ offset: 10, limit: 20 }); 73 | }); 74 | 75 | it('throws failed request', async () => { 76 | const responseHandler: ResponseHandler = (request) => { 77 | expect(request.requestHeaders['authorization']).toBeUndefined(); 78 | expect(request.queryParams).toEqual({}); 79 | expect(request.requestBody).toBeNull(); 80 | expect(request.requestHeaders['content-type']).toBe('application/json'); 81 | 82 | return [ 83 | 500, 84 | {'Content-Type': 'application/json'}, 85 | JSON.stringify({"Message": "An error has occurred."}) 86 | ]; 87 | }; 88 | server = new Pretender(function () { 89 | this.get('https://test-company.outseta.com/api/v1/billing/planfamilies', responseHandler); 90 | }); 91 | 92 | let exception; 93 | let response; 94 | 95 | try { 96 | response = await planFamilies.getAll(); 97 | } catch (e) { 98 | exception = e; 99 | } 100 | 101 | expect(response).toBeUndefined(); 102 | expect(exception.status).toBe(500); 103 | }); 104 | }); 105 | }); 106 | }); 107 | }); 108 | 109 | const exampleResponse = { 110 | "metadata": { 111 | "limit": 100, 112 | "offset": 0, 113 | "total": 1 114 | }, 115 | "items": [ 116 | { 117 | "Name": "Main Plans", 118 | "IsActive": true, 119 | "Plans": [ 120 | { 121 | "Name": "Basic", 122 | "Description": "

All the important stuff.

", 123 | "PlanFamily": null, 124 | "IsQuantityEditable": false, 125 | "MinimumQuantity": 0, 126 | "MonthlyRate": 49.99, 127 | "AnnualRate": 0.00, 128 | "SetupFee": 0.00, 129 | "IsTaxable": false, 130 | "IsActive": true, 131 | "TrialPeriodDays": 7, 132 | "UnitOfMeasure": "", 133 | "PlanAddOns": null, 134 | "ContentGroups": null, 135 | "NumberOfSubscriptions": 0, 136 | "Uid": "wZmNw7Q2", 137 | "Created": "2020-11-13T03:44:06", 138 | "Updated": "2021-01-19T04:09:42" 139 | }, 140 | { 141 | "Name": "Pro", 142 | "Description": "

For the true professionals.

", 143 | "PlanFamily": null, 144 | "IsQuantityEditable": false, 145 | "MinimumQuantity": 0, 146 | "MonthlyRate": 149.99, 147 | "AnnualRate": 0.00, 148 | "SetupFee": 0.00, 149 | "IsTaxable": false, 150 | "IsActive": true, 151 | "TrialPeriodDays": 7, 152 | "UnitOfMeasure": "", 153 | "PlanAddOns": null, 154 | "ContentGroups": null, 155 | "NumberOfSubscriptions": 0, 156 | "Uid": "y7maaKmE", 157 | "Created": "2020-11-13T03:44:42", 158 | "Updated": "2021-01-19T04:00:23" 159 | }, 160 | { 161 | "Name": "Business", 162 | "Description": "

When your team expands, so can we.

", 163 | "PlanFamily": null, 164 | "IsQuantityEditable": false, 165 | "MinimumQuantity": 0, 166 | "MonthlyRate": 299.99, 167 | "AnnualRate": 0.00, 168 | "SetupFee": 0.00, 169 | "IsTaxable": false, 170 | "IsActive": true, 171 | "TrialPeriodDays": 7, 172 | "UnitOfMeasure": "", 173 | "PlanAddOns": null, 174 | "ContentGroups": null, 175 | "NumberOfSubscriptions": 0, 176 | "Uid": "Z496J79X", 177 | "Created": "2020-11-13T03:45:02", 178 | "Updated": "2021-01-19T04:00:14" 179 | } 180 | ], 181 | "Uid": "Jy9gw09M", 182 | "Created": "2020-11-13T03:43:34", 183 | "Updated": "2020-11-13T03:46:18" 184 | } 185 | ] 186 | }; 187 | 188 | const exampleNoResults = { 189 | "metadata": { 190 | "limit": 100, 191 | "offset": 0, 192 | "total": 0 193 | }, 194 | "items": [] 195 | }; 196 | -------------------------------------------------------------------------------- /test/api/billing/subscriptions/get-all.spec.ts: -------------------------------------------------------------------------------- 1 | import Pretender, { ResponseHandler } from 'pretender'; 2 | import { Store } from '../../../../src/util/store'; 3 | import { UserCredentials, ServerCredentials } from '../../../../src/util/credentials'; 4 | import { Subscriptions } from '../../../../src/api/billing/subscriptions'; 5 | 6 | describe('api', () => { 7 | describe('Billing', () => { 8 | describe('Subscriptions', () => { 9 | describe('getAll', () => { 10 | let server: Pretender; 11 | let store: Store; 12 | 13 | let subscriptions: Subscriptions; 14 | 15 | beforeEach(() => { 16 | if (server) server.shutdown(); 17 | 18 | store = new Store( 19 | 'https://test-company.outseta.com/api/v1/', 20 | new UserCredentials(), 21 | new ServerCredentials('example_key', 'example_secret') 22 | ); 23 | 24 | subscriptions = new Subscriptions(store); 25 | }); 26 | 27 | afterAll(() => { 28 | server.shutdown(); 29 | }); 30 | 31 | it('handles successful request', async () => { 32 | const responseHandler: ResponseHandler = (request) => { 33 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 34 | expect(request.queryParams).toEqual({ fields: '*,Account.Uid,Plan.Uid' }); 35 | expect(request.requestBody).toBeNull(); 36 | expect(request.requestHeaders['content-type']).toBe('application/json'); 37 | 38 | return [ 39 | 200, 40 | {'Content-Type': 'application/json'}, 41 | JSON.stringify(exampleResponse) 42 | ]; 43 | }; 44 | server = new Pretender(function () { 45 | this.get('https://test-company.outseta.com/api/v1/billing/subscriptions', responseHandler); 46 | }); 47 | 48 | const response = await subscriptions.getAll(); 49 | expect(response.metadata.total).toBe(1); 50 | expect(response.items).toHaveSize(1); 51 | expect(response.items[0].Account.Uid).toBe("nmDAv0Qy"); 52 | }); 53 | 54 | it('handles request with pagination, filters, fields', async () => { 55 | const responseHandler: ResponseHandler = (request) => { 56 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 57 | expect(request.queryParams).toEqual({ 58 | offset: '10', 59 | limit: '20', 60 | 'Account.Uid': 'jW7GJVWq', 61 | fields: '*' 62 | }); 63 | expect(request.requestBody).toBeNull(); 64 | expect(request.requestHeaders['content-type']).toBe('application/json'); 65 | 66 | return [ 67 | 200, 68 | {'Content-Type': 'application/json'}, 69 | JSON.stringify(exampleNoResults) 70 | ]; 71 | }; 72 | server = new Pretender(function () { 73 | this.get('https://test-company.outseta.com/api/v1/billing/subscriptions', responseHandler); 74 | }); 75 | 76 | const response = await subscriptions.getAll({ 77 | offset: 10, 78 | limit: 20, 79 | Account: { 80 | Uid: 'jW7GJVWq' 81 | }, 82 | fields: '*' 83 | }); 84 | expect(response.metadata.total).toBe(0); 85 | expect(response.items).toHaveSize(0); 86 | }); 87 | 88 | it('throws failed request', async () => { 89 | const responseHandler: ResponseHandler = (request) => { 90 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 91 | expect(request.queryParams).toEqual({ 92 | fields: '*,Account.Uid,Plan.Uid' 93 | }); 94 | expect(request.requestBody).toBeNull(); 95 | expect(request.requestHeaders['content-type']).toBe('application/json'); 96 | 97 | return [ 98 | 500, 99 | {'Content-Type': 'application/json'}, 100 | JSON.stringify(exampleFailureResponse) 101 | ]; 102 | }; 103 | server = new Pretender(function () { 104 | this.get('https://test-company.outseta.com/api/v1/billing/subscriptions', responseHandler); 105 | }); 106 | 107 | let exception; 108 | let response; 109 | 110 | try { 111 | response = await subscriptions.getAll(); 112 | } catch (e) { 113 | exception = e; 114 | } 115 | 116 | expect(response).toBeUndefined(); 117 | expect(exception.status).toBe(500); 118 | }); 119 | }); 120 | }); 121 | }); 122 | }); 123 | 124 | const exampleResponse = { 125 | "metadata": { 126 | "limit": 100, 127 | "offset": 0, 128 | "total": 1 129 | }, 130 | "items": [ 131 | { 132 | "BillingRenewalTerm": 1, 133 | "Account": { 134 | "Uid": "nmDAv0Qy" 135 | }, 136 | "Plan": { 137 | "Uid": "wZmNw7Q2" 138 | }, 139 | "Quantity": null, 140 | "StartDate": "2021-02-07T21:58:52", 141 | "EndDate": null, 142 | "RenewalDate": "2021-02-14T21:58:52", 143 | "NewRequiredQuantity": null, 144 | "IsPlanUpgradeRequired": false, 145 | "PlanUpgradeRequiredMessage": null, 146 | "SubscriptionAddOns": null, 147 | "DiscountCouponSubscriptions": null, 148 | "Uid": "dQGxEz94", 149 | "Created": "2021-02-07T21:58:52", 150 | "Updated": "2021-02-07T21:58:52" 151 | } 152 | ] 153 | }; 154 | 155 | const exampleNoResults = { 156 | "metadata": { 157 | "limit": 100, 158 | "offset": 0, 159 | "total": 0 160 | }, 161 | "items": [] 162 | }; 163 | 164 | const exampleFailureResponse = { 165 | "ErrorMessage": "Could not convert hash to long", 166 | "EntityValidationErrors": [] 167 | }; 168 | -------------------------------------------------------------------------------- /test/api/billing/subscriptions/get.spec.ts: -------------------------------------------------------------------------------- 1 | import Pretender, { ResponseHandler } from 'pretender'; 2 | import { Store } from '../../../../src/util/store'; 3 | import { UserCredentials, ServerCredentials } from '../../../../src/util/credentials'; 4 | import { Subscriptions } from '../../../../src/api/billing/subscriptions'; 5 | 6 | describe('api', () => { 7 | describe('Billing', () => { 8 | describe('Subscriptions', () => { 9 | describe('get', () => { 10 | let server: Pretender; 11 | let store: Store; 12 | 13 | let subscriptions: Subscriptions; 14 | 15 | beforeEach(() => { 16 | if (server) server.shutdown(); 17 | 18 | store = new Store( 19 | 'https://test-company.outseta.com/api/v1/', 20 | new UserCredentials(), 21 | new ServerCredentials('example_key', 'example_secret') 22 | ); 23 | 24 | subscriptions = new Subscriptions(store); 25 | }); 26 | 27 | afterAll(() => { 28 | server.shutdown(); 29 | }); 30 | 31 | it('handles successful request', async () => { 32 | const responseHandler: ResponseHandler = (request) => { 33 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 34 | expect(request.queryParams).toEqual({ fields: '*,Account.Uid,Plan.Uid' }); 35 | expect(request.requestBody).toBeNull(); 36 | expect(request.requestHeaders['content-type']).toBe('application/json'); 37 | 38 | return [ 39 | 200, 40 | {'Content-Type': 'application/json'}, 41 | JSON.stringify(exampleResponse) 42 | ]; 43 | }; 44 | server = new Pretender(function () { 45 | this.get('https://test-company.outseta.com/api/v1/billing/subscriptions/rmkG72mg', responseHandler); 46 | }); 47 | 48 | const response = await subscriptions.get('rmkG72mg'); 49 | expect(response.Account.Name).toBe('TiltCamp'); 50 | }); 51 | 52 | it('handles request with fields', async () => { 53 | const responseHandler: ResponseHandler = (request) => { 54 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 55 | expect(request.queryParams).toEqual({ fields: '*' }); 56 | expect(request.requestBody).toBeNull(); 57 | expect(request.requestHeaders['content-type']).toBe('application/json'); 58 | 59 | return [ 60 | 200, 61 | {'Content-Type': 'application/json'}, 62 | JSON.stringify(exampleResponse) 63 | ]; 64 | }; 65 | server = new Pretender(function () { 66 | this.get('https://test-company.outseta.com/api/v1/billing/subscriptions/rmkG72mg', responseHandler); 67 | }); 68 | 69 | const response = await subscriptions.get('rmkG72mg', { fields: '*' }); 70 | expect(response.BillingRenewalTerm).toBe(1); 71 | }); 72 | 73 | it('throws failed request', async () => { 74 | const responseHandler: ResponseHandler = (request) => { 75 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 76 | expect(request.queryParams).toEqual({ fields: '*,Account.Uid,Plan.Uid' }); 77 | expect(request.requestBody).toBeNull(); 78 | expect(request.requestHeaders['content-type']).toBe('application/json'); 79 | 80 | return [ 81 | 500, 82 | {'Content-Type': 'application/json'}, 83 | JSON.stringify(exampleFailure) 84 | ]; 85 | }; 86 | server = new Pretender(function () { 87 | this.get('https://test-company.outseta.com/api/v1/billing/subscriptions/rmkG72mg', responseHandler); 88 | }); 89 | 90 | let exception; 91 | let response; 92 | 93 | try { 94 | response = await subscriptions.get('rmkG72mg'); 95 | } catch (e) { 96 | exception = e; 97 | } 98 | 99 | expect(response).toBeUndefined(); 100 | expect(exception.status).toBe(500); 101 | }); 102 | }); 103 | }); 104 | }); 105 | }); 106 | 107 | const exampleResponse = { 108 | "BillingRenewalTerm": 1, 109 | "Account": { 110 | "Name": "TiltCamp", 111 | "ClientIdentifier": null, 112 | "IsDemo": false, 113 | "BillingAddress": null, 114 | "MailingAddress": null, 115 | "AccountStage": 3, 116 | "PaymentInformation": null, 117 | "PersonAccount": null, 118 | "Subscriptions": null, 119 | "Deals": null, 120 | "LastLoginDateTime": null, 121 | "AccountSpecificPageUrl1": null, 122 | "AccountSpecificPageUrl2": null, 123 | "AccountSpecificPageUrl3": null, 124 | "AccountSpecificPageUrl4": null, 125 | "AccountSpecificPageUrl5": null, 126 | "RewardFulReferralId": null, 127 | "HasLoggedIn": false, 128 | "AccountStageLabel": "Subscribing", 129 | "DomainName": null, 130 | "LatestSubscription": null, 131 | "CurrentSubscription": null, 132 | "PrimaryContact": null, 133 | "PrimarySubscription": null, 134 | "RecaptchaToken": null, 135 | "LifetimeRevenue": 0.0, 136 | "Uid": "BWz8JPQE", 137 | "Created": "2021-02-04T16:22:11", 138 | "Updated": "2021-02-06T18:09:24" 139 | }, 140 | "Plan": { 141 | "Name": "Basic", 142 | "Description": "

All the important stuff.

", 143 | "PlanFamily": null, 144 | "IsQuantityEditable": false, 145 | "MinimumQuantity": 0, 146 | "MonthlyRate": 49.99, 147 | "AnnualRate": 0.00, 148 | "SetupFee": 0.00, 149 | "IsTaxable": false, 150 | "IsActive": true, 151 | "TrialPeriodDays": 7, 152 | "UnitOfMeasure": "", 153 | "PlanAddOns": null, 154 | "ContentGroups": null, 155 | "NumberOfSubscriptions": 0, 156 | "Uid": "wZmNw7Q2", 157 | "Created": "2020-11-13T03:44:06", 158 | "Updated": "2021-01-19T04:09:42" 159 | }, 160 | "Quantity": null, 161 | "StartDate": "2021-02-06T17:49:51", 162 | "EndDate": "2021-02-06T18:09:24", 163 | "RenewalDate": null, 164 | "NewRequiredQuantity": null, 165 | "IsPlanUpgradeRequired": false, 166 | "PlanUpgradeRequiredMessage": null, 167 | "SubscriptionAddOns": [ 168 | { 169 | "BillingRenewalTerm": 1, 170 | "Subscription": null, 171 | "AddOn": null, 172 | "Quantity": null, 173 | "StartDate": "2021-02-06T17:49:51", 174 | "EndDate": "2021-02-06T18:09:24", 175 | "RenewalDate": null, 176 | "NewRequiredQuantity": null, 177 | "Uid": "OW4kvY9g", 178 | "Created": "2021-02-06T17:49:51", 179 | "Updated": "2021-02-06T18:09:24" 180 | } 181 | ], 182 | "DiscountCouponSubscriptions": [ 183 | 184 | ], 185 | "Uid": "rmkG72mg", 186 | "Created": "2021-02-06T17:49:51", 187 | "Updated": "2021-02-06T18:09:24" 188 | }; 189 | 190 | const exampleFailure = { 191 | "ErrorMessage": "Could not convert hash g to long", 192 | "EntityValidationErrors" :[] 193 | }; 194 | -------------------------------------------------------------------------------- /test/api/crm/accounts/delete.spec.ts: -------------------------------------------------------------------------------- 1 | import Pretender, { ResponseHandler } from 'pretender'; 2 | import { Store } from '../../../../src/util/store'; 3 | import { Accounts } from '../../../../src/api/crm/accounts'; 4 | 5 | import { ServerCredentials, UserCredentials } from '../../../../src/util/credentials'; 6 | 7 | describe('api', () => { 8 | describe('Crm', () => { 9 | describe('Accounts', () => { 10 | describe('delete', () => { 11 | let server: Pretender; 12 | let store: Store; 13 | 14 | let accounts: Accounts; 15 | 16 | beforeEach(() => { 17 | if (server) server.shutdown(); 18 | 19 | store = new Store( 20 | 'https://test-company.outseta.com/api/v1/', 21 | new UserCredentials(), 22 | new ServerCredentials('example_key', 'example_secret') 23 | ); 24 | 25 | accounts = new Accounts(store); 26 | }); 27 | 28 | afterAll(() => { 29 | server.shutdown(); 30 | }); 31 | 32 | it('handles successful request', async () => { 33 | const responseHandler: ResponseHandler = (request) => { 34 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 35 | expect(request.queryParams).toEqual({}); 36 | expect(request.requestBody).toBeNull(); 37 | expect(request.requestHeaders['content-type']).toBe('application/json'); 38 | 39 | return [ 40 | 200, 41 | {'Content-Type': 'application/json'}, 42 | '' 43 | ]; 44 | }; 45 | server = new Pretender(function () { 46 | this.delete('https://test-company.outseta.com/api/v1/crm/accounts/BWz87NQE', responseHandler); 47 | }); 48 | 49 | const response = await accounts.delete('BWz87NQE'); 50 | expect(response).toBeNull(); 51 | }); 52 | 53 | it('throws failed request', async () => { 54 | const responseHandler: ResponseHandler = (request) => { 55 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 56 | expect(request.queryParams).toEqual({}); 57 | expect(request.requestBody).toBeNull(); 58 | expect(request.requestHeaders['content-type']).toBe('application/json'); 59 | 60 | return [ 61 | 404, 62 | {'Content-Type': 'application/json'}, 63 | '' 64 | ]; 65 | }; 66 | server = new Pretender(function () { 67 | this.delete('https://test-company.outseta.com/api/v1/crm/accounts/BWz87NQE', responseHandler); 68 | }); 69 | 70 | let exception; 71 | let response; 72 | 73 | try { 74 | response = await accounts.delete('BWz87NQE'); 75 | } catch (e) { 76 | exception = e; 77 | } 78 | 79 | expect(response).toBeUndefined(); 80 | expect(exception.status).toBe(404); 81 | }); 82 | }); 83 | }); 84 | }); 85 | }); 86 | -------------------------------------------------------------------------------- /test/api/crm/accounts/get-all.spec.ts: -------------------------------------------------------------------------------- 1 | import Pretender, { ResponseHandler } from 'pretender'; 2 | import { Store } from '../../../../src/util/store'; 3 | import { Accounts } from '../../../../src/api/crm/accounts'; 4 | 5 | import { ServerCredentials, UserCredentials } from '../../../../src/util/credentials'; 6 | import { AccountStage } from '../../../../src/models/crm/account-stage'; 7 | 8 | describe('api', () => { 9 | describe('Crm', () => { 10 | describe('Accounts', () => { 11 | describe('getAll', () => { 12 | let server: Pretender; 13 | let store: Store; 14 | 15 | let accounts: Accounts; 16 | 17 | beforeEach(() => { 18 | if (server) server.shutdown(); 19 | 20 | store = new Store( 21 | 'https://test-company.outseta.com/api/v1/', 22 | new UserCredentials(), 23 | new ServerCredentials('example_key', 'example_secret') 24 | ); 25 | 26 | accounts = new Accounts(store); 27 | }); 28 | 29 | afterAll(() => { 30 | server.shutdown(); 31 | }); 32 | 33 | it('handles successful request', async () => { 34 | const responseHandler: ResponseHandler = (request) => { 35 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 36 | expect(request.queryParams).toEqual({ fields: '*,PersonAccount.*,PersonAccount.Person.Uid' }); 37 | expect(request.requestBody).toBeNull(); 38 | expect(request.requestHeaders['content-type']).toBe('application/json'); 39 | 40 | return [ 41 | 200, 42 | {'Content-Type': 'application/json'}, 43 | JSON.stringify(exampleResponse) 44 | ]; 45 | }; 46 | server = new Pretender(function () { 47 | this.get('https://test-company.outseta.com/api/v1/crm/accounts', responseHandler); 48 | }); 49 | 50 | const response = await accounts.getAll(); 51 | expect(response.metadata.total).toBe(1); 52 | expect(response.items).toHaveSize(1); 53 | expect(response.items[0].Name).toBe('Demo Account'); 54 | }); 55 | 56 | it('handles request with pagination, filters, fields', async () => { 57 | const responseHandler: ResponseHandler = (request) => { 58 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 59 | expect(request.queryParams).toEqual({ 60 | offset: '10', 61 | limit: '20', 62 | AccountStage: '3', 63 | fields: '*' 64 | }); 65 | expect(request.requestBody).toBeNull(); 66 | expect(request.requestHeaders['content-type']).toBe('application/json'); 67 | 68 | return [ 69 | 200, 70 | {'Content-Type': 'application/json'}, 71 | JSON.stringify(exampleNoResults) 72 | ]; 73 | }; 74 | server = new Pretender(function () { 75 | this.get('https://test-company.outseta.com/api/v1/crm/accounts', responseHandler); 76 | }); 77 | 78 | const response = await accounts.getAll({ 79 | offset: 10, 80 | limit: 20, 81 | accountStage: AccountStage.Subscribing, 82 | fields: '*' 83 | }); 84 | expect(response.metadata.total).toBe(0); 85 | expect(response.items).toHaveSize(0); 86 | }); 87 | 88 | it('throws failed request', async () => { 89 | const responseHandler: ResponseHandler = (request) => { 90 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 91 | expect(request.queryParams).toEqual({ 92 | fields: '*,PersonAccount.*,PersonAccount.Person.Uid' 93 | }); 94 | expect(request.requestBody).toBeNull(); 95 | expect(request.requestHeaders['content-type']).toBe('application/json'); 96 | 97 | return [ 98 | 500, 99 | {'Content-Type': 'application/json'}, 100 | JSON.stringify({ "Message": "An error has occurred." }) 101 | ]; 102 | }; 103 | server = new Pretender(function () { 104 | this.get('https://test-company.outseta.com/api/v1/crm/accounts', responseHandler); 105 | }); 106 | 107 | let exception; 108 | let response; 109 | 110 | try { 111 | response = await accounts.getAll(); 112 | } catch (e) { 113 | exception = e; 114 | } 115 | 116 | expect(response).toBeUndefined(); 117 | expect(exception.status).toBe(500); 118 | }); 119 | }); 120 | }); 121 | }); 122 | }); 123 | 124 | const exampleResponse = { 125 | "metadata": { 126 | "limit": 100, 127 | "offset": 0, 128 | "total": 1 129 | }, 130 | "items": [ 131 | { 132 | "Name": "Demo Account", 133 | "ClientIdentifier": null, 134 | "IsDemo": true, 135 | "BillingAddress": { 136 | "AddressLine1": null, 137 | "AddressLine2": null, 138 | "AddressLine3": null, 139 | "City": "", 140 | "State": "", 141 | "PostalCode": "", 142 | "Country": null, 143 | "Uid": "VmAeg7ma", 144 | "Created": "2021-01-20T05:25:56", 145 | "Updated": "2021-01-20T05:25:56" 146 | }, 147 | "MailingAddress": { 148 | "AddressLine1": null, 149 | "AddressLine2": null, 150 | "AddressLine3": null, 151 | "City": "", 152 | "State": "", 153 | "PostalCode": "", 154 | "Country": null, 155 | "Uid": "gWKwqMWp", 156 | "Created": "2021-01-20T05:25:56", 157 | "Updated": "2021-01-20T05:25:56" 158 | }, 159 | "AccountStage": 3, 160 | "PaymentInformation": null, 161 | "PersonAccount": [ 162 | { 163 | "Person": { 164 | "Uid": "L9P6gepm" 165 | }, 166 | "Account": null, 167 | "IsPrimary": true, 168 | "Uid": "MQvL3kWY", 169 | "Created": "2021-02-04T16:22:10.8860898Z", 170 | "Updated": "2021-02-04T16:22:10.8860898Z" 171 | } 172 | ], 173 | "Subscriptions": [], 174 | "Deals": [], 175 | "LastLoginDateTime": null, 176 | "AccountSpecificPageUrl1": "", 177 | "AccountSpecificPageUrl2": "", 178 | "AccountSpecificPageUrl3": "", 179 | "AccountSpecificPageUrl4": "", 180 | "AccountSpecificPageUrl5": "", 181 | "RewardFulReferralId": null, 182 | "HasLoggedIn": false, 183 | "AccountStageLabel": "Subscribing", 184 | "DomainName": null, 185 | "LatestSubscription": null, 186 | "CurrentSubscription": null, 187 | "PrimaryContact": null, 188 | "PrimarySubscription": null, 189 | "RecaptchaToken": null, 190 | "LifetimeRevenue": 0.0, 191 | "Uid": "E9Ly3PWw", 192 | "Created": "2021-01-20T05:25:56", 193 | "Updated": "2021-01-20T05:25:56" 194 | } 195 | ] 196 | }; 197 | 198 | const exampleNoResults = { 199 | "metadata": { 200 | "limit": 100, 201 | "offset": 0, 202 | "total": 0 203 | }, 204 | "items": [] 205 | }; 206 | -------------------------------------------------------------------------------- /test/api/crm/accounts/get.spec.ts: -------------------------------------------------------------------------------- 1 | import Pretender, { ResponseHandler } from 'pretender'; 2 | import { Store } from '../../../../src/util/store'; 3 | import { Accounts } from '../../../../src/api/crm/accounts'; 4 | 5 | import { ServerCredentials, UserCredentials } from '../../../../src/util/credentials'; 6 | 7 | describe('api', () => { 8 | describe('Crm', () => { 9 | describe('Accounts', () => { 10 | describe('get', () => { 11 | let server: Pretender; 12 | let store: Store; 13 | 14 | let accounts: Accounts; 15 | 16 | beforeEach(() => { 17 | if (server) server.shutdown(); 18 | 19 | store = new Store( 20 | 'https://test-company.outseta.com/api/v1/', 21 | new UserCredentials(), 22 | new ServerCredentials('example_key', 'example_secret') 23 | ); 24 | 25 | accounts = new Accounts(store); 26 | }); 27 | 28 | afterAll(() => { 29 | server.shutdown(); 30 | }); 31 | 32 | it('handles successful request', async () => { 33 | const responseHandler: ResponseHandler = (request) => { 34 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 35 | expect(request.queryParams).toEqual({ fields: '*,PersonAccount.*,PersonAccount.Person.Uid' }); 36 | expect(request.requestBody).toBeNull(); 37 | expect(request.requestHeaders['content-type']).toBe('application/json'); 38 | 39 | return [ 40 | 200, 41 | {'Content-Type': 'application/json'}, 42 | JSON.stringify(exampleResponse) 43 | ]; 44 | }; 45 | server = new Pretender(function () { 46 | this.get('https://test-company.outseta.com/api/v1/crm/accounts/pWrYPn9n', responseHandler); 47 | }); 48 | 49 | const response = await accounts.get('pWrYPn9n'); 50 | expect(response.Name).toBe('Demo Account'); 51 | }); 52 | 53 | it('handles request with fields', async () => { 54 | const responseHandler: ResponseHandler = (request) => { 55 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 56 | expect(request.queryParams).toEqual({ fields: '*' }); 57 | expect(request.requestBody).toBeNull(); 58 | expect(request.requestHeaders['content-type']).toBe('application/json'); 59 | 60 | return [ 61 | 200, 62 | {'Content-Type': 'application/json'}, 63 | JSON.stringify(exampleResponse) 64 | ]; 65 | }; 66 | server = new Pretender(function () { 67 | this.get('https://test-company.outseta.com/api/v1/crm/accounts/pWrYPn9n', responseHandler); 68 | }); 69 | 70 | const response = await accounts.get('pWrYPn9n', { 71 | fields: '*' 72 | }); 73 | expect(response.Name).toBe('Demo Account'); 74 | }); 75 | 76 | it('throws failed request', async () => { 77 | const responseHandler: ResponseHandler = (request) => { 78 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 79 | expect(request.queryParams).toEqual({ fields: '*,PersonAccount.*,PersonAccount.Person.Uid' }); 80 | expect(request.requestBody).toBeNull(); 81 | expect(request.requestHeaders['content-type']).toBe('application/json'); 82 | 83 | return [ 84 | 500, 85 | {'Content-Type': 'application/json'}, 86 | JSON.stringify({ "Message": "An error has occurred." }) 87 | ]; 88 | }; 89 | server = new Pretender(function () { 90 | this.get('https://test-company.outseta.com/api/v1/crm/accounts/pWrYPn9n', responseHandler); 91 | }); 92 | 93 | let exception; 94 | let response; 95 | 96 | try { 97 | response = await accounts.get('pWrYPn9n'); 98 | } catch (e) { 99 | exception = e; 100 | } 101 | 102 | expect(response).toBeUndefined(); 103 | expect(exception.status).toBe(500); 104 | }); 105 | }); 106 | }); 107 | }); 108 | }); 109 | 110 | const exampleResponse = { 111 | "Name": "Demo Account", 112 | "ClientIdentifier": null, 113 | "IsDemo": true, 114 | "BillingAddress": { 115 | "AddressLine1": null, 116 | "AddressLine2": null, 117 | "AddressLine3": null, 118 | "City": "", 119 | "State": "", 120 | "PostalCode": "", 121 | "Country": null, 122 | "Uid": "VmAeg7ma", 123 | "Created": "2021-01-20T05:25:56", 124 | "Updated": "2021-01-20T05:25:56" 125 | }, 126 | "MailingAddress": { 127 | "AddressLine1": null, 128 | "AddressLine2": null, 129 | "AddressLine3": null, 130 | "City": "", 131 | "State": "", 132 | "PostalCode": "", 133 | "Country": null, 134 | "Uid": "gWKwqMWp", 135 | "Created": "2021-01-20T05:25:56", 136 | "Updated": "2021-01-20T05:25:56" 137 | }, 138 | "AccountStage": 3, 139 | "PaymentInformation": null, 140 | "PersonAccount": [ 141 | { 142 | "Person": { 143 | "Uid": "L9P6gepm" 144 | }, 145 | "Account": null, 146 | "IsPrimary": true, 147 | "Uid": "MQvL3kWY", 148 | "Created": "2021-02-04T16:22:10.8860898Z", 149 | "Updated": "2021-02-04T16:22:10.8860898Z" 150 | } 151 | ], 152 | "Subscriptions": [ 153 | 154 | ], 155 | "Deals": [ 156 | 157 | ], 158 | "LastLoginDateTime": null, 159 | "AccountSpecificPageUrl1": "", 160 | "AccountSpecificPageUrl2": "", 161 | "AccountSpecificPageUrl3": "", 162 | "AccountSpecificPageUrl4": "", 163 | "AccountSpecificPageUrl5": "", 164 | "RewardFulReferralId": null, 165 | "HasLoggedIn": false, 166 | "AccountStageLabel": "Subscribing", 167 | "DomainName": null, 168 | "LatestSubscription": null, 169 | "CurrentSubscription": null, 170 | "PrimaryContact": null, 171 | "PrimarySubscription": null, 172 | "RecaptchaToken": null, 173 | "LifetimeRevenue": 0.0, 174 | "Uid": "E9Ly3PWw", 175 | "Created": "2021-01-20T05:25:56", 176 | "Updated": "2021-01-20T05:25:56" 177 | }; 178 | -------------------------------------------------------------------------------- /test/api/crm/activities/get-all.spec.ts: -------------------------------------------------------------------------------- 1 | import Pretender, { ResponseHandler } from 'pretender'; 2 | import { Activities } from '../../../../src/api/crm/activities'; 3 | import { ActivityType } from '../../../../src/models/crm/activity-type'; 4 | import { EntityType } from '../../../../src/models/shared/entity-type'; 5 | import { ServerCredentials, UserCredentials } from '../../../../src/util/credentials'; 6 | import { Store } from '../../../../src/util/store'; 7 | 8 | describe('api', () => { 9 | describe('Crm', () => { 10 | describe('Activities', () => { 11 | describe('getAll', () => { 12 | let server: Pretender; 13 | let store: Store; 14 | 15 | let activities: Activities; 16 | 17 | beforeEach(() => { 18 | if (server) server.shutdown(); 19 | 20 | store = new Store( 21 | 'https://test-company.outseta.com/api/v1/', 22 | new UserCredentials(), 23 | new ServerCredentials('example_key', 'example_secret') 24 | ); 25 | 26 | activities = new Activities(store); 27 | }); 28 | 29 | afterAll(() => { 30 | server.shutdown(); 31 | }); 32 | 33 | it('handles successful request', async () => { 34 | const responseHandler: ResponseHandler = (request) => { 35 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 36 | expect(request.queryParams).toEqual({ fields: Activities.DEFAULT_FIELDS }); 37 | expect(request.requestBody).toBeNull(); 38 | expect(request.requestHeaders['content-type']).toBe('application/json'); 39 | 40 | return [ 41 | 200, 42 | {'Content-Type': 'application/json'}, 43 | JSON.stringify(exampleResponse) 44 | ]; 45 | }; 46 | server = new Pretender(function () { 47 | this.get('https://test-company.outseta.com/api/v1/activities', responseHandler); 48 | }); 49 | 50 | const response = await activities.getAll(); 51 | expect(response.metadata.total).toBe(3); 52 | expect(response.items).toHaveSize(3); 53 | expect(response.items[0].Uid).toBe("XQYOGZeW"); 54 | expect(response.items[0].Title).toBe('Account created'); 55 | expect(response.items[0].Description).toBe('TiltCamp and Friends created'); 56 | }); 57 | 58 | it('handles request with pagination, filters, fields', async () => { 59 | const responseHandler: ResponseHandler = (request) => { 60 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 61 | expect(request.queryParams).toEqual({ 62 | offset: '10', 63 | limit: '20', 64 | fields: 'Uid', 65 | ActivityType: '102', 66 | EntityType: '2', 67 | EntityUid: 'DQ2DyknW' 68 | }); 69 | expect(request.requestBody).toBeNull(); 70 | expect(request.requestHeaders['content-type']).toBe('application/json'); 71 | 72 | return [ 73 | 200, 74 | {'Content-Type': 'application/json'}, 75 | JSON.stringify(exampleNoResults) 76 | ]; 77 | }; 78 | server = new Pretender(function () { 79 | this.get('https://test-company.outseta.com/api/v1/activities', responseHandler); 80 | }); 81 | 82 | const response = await activities.getAll({ 83 | offset: 10, 84 | limit: 20, 85 | fields: 'Uid', 86 | ActivityType: ActivityType.AccountAddPerson, 87 | EntityType: EntityType.Person, 88 | EntityUid: 'DQ2DyknW' 89 | }); 90 | expect(response.metadata.total).toBe(0); 91 | expect(response.items).toHaveSize(0); 92 | }); 93 | 94 | it('throws failed request', async () => { 95 | const responseHandler: ResponseHandler = (request) => { 96 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 97 | expect(request.queryParams).toEqual({ fields: Activities.DEFAULT_FIELDS }); 98 | expect(request.requestBody).toBeNull(); 99 | expect(request.requestHeaders['content-type']).toBe('application/json'); 100 | 101 | return [ 102 | 500, 103 | {'Content-Type': 'application/json'}, 104 | JSON.stringify({ "Message": "An error has occurred." }) 105 | ]; 106 | }; 107 | server = new Pretender(function () { 108 | this.get('https://test-company.outseta.com/api/v1/activities', responseHandler); 109 | }); 110 | 111 | let exception; 112 | let response; 113 | 114 | try { 115 | response = await activities.getAll(); 116 | } catch (e) { 117 | exception = e; 118 | } 119 | 120 | expect(response).toBeUndefined(); 121 | expect(exception.status).toBe(500); 122 | }); 123 | }); 124 | }); 125 | }); 126 | }); 127 | 128 | const exampleResponse = { 129 | "metadata": { 130 | "limit": 3, 131 | "offset": 0, 132 | "total": 3 133 | }, 134 | "items": [ 135 | { 136 | "Title": "Account created", 137 | "Description": "TiltCamp and Friends created", 138 | "ActivityData": null, 139 | "ActivityDateTime": "2021-02-13T05:08:22", 140 | "ActivityType": 100, 141 | "EntityType": 1, 142 | "EntityUid": "amRdzZ9J", 143 | "Uid": "XQYOGZeW", 144 | "Created": "2021-02-13T05:08:22", 145 | "Updated": "2021-02-13T05:08:22" 146 | }, 147 | { 148 | "Title": "Account created", 149 | "Description": "TiltCamp created", 150 | "ActivityData": null, 151 | "ActivityDateTime": "2021-02-01T16:40:25", 152 | "ActivityType": 100, 153 | "EntityType": 1, 154 | "EntityUid": "wQX4LxQK", 155 | "Uid": "y9qbpxNW", 156 | "Created": "2021-02-01T16:40:25", 157 | "Updated": "2021-02-01T16:40:25" 158 | }, 159 | { 160 | "Title": "Account created", 161 | "Description": "TiltCamp created", 162 | "ActivityData": null, 163 | "ActivityDateTime": "2021-02-06T21:59:17", 164 | "ActivityType": 100, 165 | "EntityType": 1, 166 | "EntityUid": "pWrK8Kmn", 167 | "Uid": "yW10A2P9", 168 | "Created": "2021-02-06T21:59:17", 169 | "Updated": "2021-02-06T21:59:17" 170 | } 171 | ] 172 | }; 173 | 174 | const exampleNoResults = { 175 | "metadata": { 176 | "limit": 100, 177 | "offset": 0, 178 | "total": 0 179 | }, 180 | "items": [] 181 | }; 182 | -------------------------------------------------------------------------------- /test/api/crm/deals/delete.spec.ts: -------------------------------------------------------------------------------- 1 | import Pretender, { ResponseHandler } from 'pretender'; 2 | import { Store } from '../../../../src/util/store'; 3 | import { Deals } from '../../../../src/api/crm/deals'; 4 | 5 | import { ServerCredentials, UserCredentials } from '../../../../src/util/credentials'; 6 | 7 | describe('api', () => { 8 | describe('Crm', () => { 9 | describe('Deals', () => { 10 | describe('delete', () => { 11 | let server: Pretender; 12 | let store: Store; 13 | 14 | let deals: Deals; 15 | 16 | beforeEach(() => { 17 | if (server) server.shutdown(); 18 | 19 | store = new Store( 20 | 'https://test-company.outseta.com/api/v1/', 21 | new UserCredentials(), 22 | new ServerCredentials('example_key', 'example_secret') 23 | ); 24 | 25 | deals = new Deals(store); 26 | }); 27 | 28 | afterAll(() => { 29 | server.shutdown(); 30 | }); 31 | 32 | it('handles successful request', async () => { 33 | const responseHandler: ResponseHandler = (request) => { 34 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 35 | expect(request.queryParams).toEqual({}); 36 | expect(request.requestBody).toBeNull(); 37 | expect(request.requestHeaders['content-type']).toBe('application/json'); 38 | 39 | return [ 40 | 200, 41 | {'Content-Type': 'application/json'}, 42 | '' 43 | ]; 44 | }; 45 | server = new Pretender(function () { 46 | this.delete('https://test-company.outseta.com/api/v1/crm/deals/wmjvOlmV', responseHandler); 47 | }); 48 | 49 | const response = await deals.delete('wmjvOlmV'); 50 | expect(response).toBeNull(); 51 | }); 52 | 53 | it('throws failed request', async () => { 54 | const responseHandler: ResponseHandler = (request) => { 55 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 56 | expect(request.queryParams).toEqual({}); 57 | expect(request.requestBody).toBeNull(); 58 | expect(request.requestHeaders['content-type']).toBe('application/json'); 59 | 60 | return [ 61 | 404, 62 | {'Content-Type': 'application/json'}, 63 | '' 64 | ]; 65 | }; 66 | server = new Pretender(function () { 67 | this.delete('https://test-company.outseta.com/api/v1/crm/deals/wmjvOlmV', responseHandler); 68 | }); 69 | 70 | let exception; 71 | let response; 72 | 73 | try { 74 | response = await deals.delete('wmjvOlmV'); 75 | } catch (e) { 76 | exception = e; 77 | } 78 | 79 | expect(response).toBeUndefined(); 80 | expect(exception.status).toBe(404); 81 | }); 82 | }); 83 | }); 84 | }); 85 | }); 86 | -------------------------------------------------------------------------------- /test/api/crm/deals/get-all.spec.ts: -------------------------------------------------------------------------------- 1 | import Pretender, { ResponseHandler } from 'pretender'; 2 | import { Store } from '../../../../src/util/store'; 3 | import { Deals } from '../../../../src/api/crm/deals'; 4 | 5 | import { ServerCredentials, UserCredentials } from '../../../../src/util/credentials'; 6 | 7 | describe('api', () => { 8 | describe('Crm', () => { 9 | describe('Deals', () => { 10 | describe('getAll', () => { 11 | let server: Pretender; 12 | let store: Store; 13 | 14 | let deals: Deals; 15 | 16 | beforeEach(() => { 17 | if (server) server.shutdown(); 18 | 19 | store = new Store( 20 | 'https://test-company.outseta.com/api/v1/', 21 | new UserCredentials(), 22 | new ServerCredentials('example_key', 'example_secret') 23 | ); 24 | 25 | deals = new Deals(store); 26 | }); 27 | 28 | afterAll(() => { 29 | server.shutdown(); 30 | }); 31 | 32 | it('handles successful request', async () => { 33 | const responseHandler: ResponseHandler = (request) => { 34 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 35 | expect(request.queryParams).toEqual({ fields: Deals.DEFAULT_FIELDS }); 36 | expect(request.requestBody).toBeNull(); 37 | expect(request.requestHeaders['content-type']).toBe('application/json'); 38 | 39 | return [ 40 | 200, 41 | {'Content-Type': 'application/json'}, 42 | JSON.stringify(exampleResponse) 43 | ]; 44 | }; 45 | server = new Pretender(function () { 46 | this.get('https://test-company.outseta.com/api/v1/crm/deals', responseHandler); 47 | }); 48 | 49 | const response = await deals.getAll(); 50 | expect(response.metadata.total).toBe(1); 51 | expect(response.items).toHaveSize(1); 52 | expect(response.items[0].Name).toBe('Test Deal 1'); 53 | }); 54 | 55 | it('handles request with pagination, filters, fields', async () => { 56 | const responseHandler: ResponseHandler = (request) => { 57 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 58 | expect(request.queryParams).toEqual({ 59 | offset: '10', 60 | limit: '20', 61 | fields: 'Uid', 62 | 'DealPipelineStage.Uid': 'Jy9gvRQM' 63 | }); 64 | expect(request.requestBody).toBeNull(); 65 | expect(request.requestHeaders['content-type']).toBe('application/json'); 66 | 67 | return [ 68 | 200, 69 | {'Content-Type': 'application/json'}, 70 | JSON.stringify(exampleNoResults) 71 | ]; 72 | }; 73 | server = new Pretender(function () { 74 | this.get('https://test-company.outseta.com/api/v1/crm/deals', responseHandler); 75 | }); 76 | 77 | const response = await deals.getAll({ 78 | offset: 10, 79 | limit: 20, 80 | fields: 'Uid', 81 | DealPipelineStage: { 82 | Uid: 'Jy9gvRQM' 83 | } 84 | }); 85 | expect(response.metadata.total).toBe(0); 86 | expect(response.items).toHaveSize(0); 87 | }); 88 | 89 | it('throws failed request', async () => { 90 | const responseHandler: ResponseHandler = (request) => { 91 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 92 | expect(request.queryParams).toEqual({ fields: Deals.DEFAULT_FIELDS }); 93 | expect(request.requestBody).toBeNull(); 94 | expect(request.requestHeaders['content-type']).toBe('application/json'); 95 | 96 | return [ 97 | 500, 98 | {'Content-Type': 'application/json'}, 99 | JSON.stringify({ "Message": "An error has occurred." }) 100 | ]; 101 | }; 102 | server = new Pretender(function () { 103 | this.get('https://test-company.outseta.com/api/v1/crm/deals', responseHandler); 104 | }); 105 | 106 | let exception; 107 | let response; 108 | 109 | try { 110 | response = await deals.getAll(); 111 | } catch (e) { 112 | exception = e; 113 | } 114 | 115 | expect(response).toBeUndefined(); 116 | expect(exception.status).toBe(500); 117 | }); 118 | }); 119 | }); 120 | }); 121 | }); 122 | 123 | const exampleResponse = { 124 | "metadata": { 125 | "limit": 100, 126 | "offset": 0, 127 | "total": 1 128 | }, 129 | "items": [ 130 | { 131 | "Name": "Test Deal 1", 132 | "Amount": 100000.00, 133 | "DueDate": "2021-02-15T08:00:00", 134 | "AssignedToPersonClientIdentifier": "Rm8rb7y9", 135 | "Weight": 0, 136 | "DealPipelineStage": null, 137 | "Account": null, 138 | "DealPeople": null, 139 | "Contacts": "hello@tiltcamp.com", 140 | "Owner": null, 141 | "Uid": "Rm8pvym4", 142 | "Created": "2021-02-15T20:59:07", 143 | "Updated": "2021-02-15T20:59:07" 144 | } 145 | ] 146 | }; 147 | 148 | const exampleNoResults = { 149 | "metadata": { 150 | "limit": 100, 151 | "offset": 0, 152 | "total": 0 153 | }, 154 | "items": [] 155 | }; 156 | -------------------------------------------------------------------------------- /test/api/crm/deals/get.spec.ts: -------------------------------------------------------------------------------- 1 | import Pretender, { ResponseHandler } from 'pretender'; 2 | import { Store } from '../../../../src/util/store'; 3 | import { Deals } from '../../../../src/api/crm/deals'; 4 | 5 | import { ServerCredentials, UserCredentials } from '../../../../src/util/credentials'; 6 | 7 | describe('api', () => { 8 | describe('Crm', () => { 9 | describe('Deals', () => { 10 | describe('get', () => { 11 | let server: Pretender; 12 | let store: Store; 13 | 14 | let deals: Deals; 15 | 16 | beforeEach(() => { 17 | if (server) server.shutdown(); 18 | 19 | store = new Store( 20 | 'https://test-company.outseta.com/api/v1/', 21 | new UserCredentials(), 22 | new ServerCredentials('example_key', 'example_secret') 23 | ); 24 | 25 | deals = new Deals(store); 26 | }); 27 | 28 | afterAll(() => { 29 | server.shutdown(); 30 | }); 31 | 32 | it('handles successful request', async () => { 33 | const responseHandler: ResponseHandler = (request) => { 34 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 35 | expect(request.queryParams).toEqual({ fields: Deals.DEFAULT_FIELDS }); 36 | expect(request.requestBody).toBeNull(); 37 | expect(request.requestHeaders['content-type']).toBe('application/json'); 38 | 39 | return [ 40 | 200, 41 | {'Content-Type': 'application/json'}, 42 | JSON.stringify(exampleResponse) 43 | ]; 44 | }; 45 | server = new Pretender(function () { 46 | this.get('https://test-company.outseta.com/api/v1/crm/deals/pWrYPn9n', responseHandler); 47 | }); 48 | 49 | const response = await deals.get('pWrYPn9n'); 50 | expect(response.Name).toBe('Test Deal 1'); 51 | }); 52 | 53 | it('handles request with fields', async () => { 54 | const responseHandler: ResponseHandler = (request) => { 55 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 56 | expect(request.queryParams).toEqual({ fields: 'Uid' }); 57 | expect(request.requestBody).toBeNull(); 58 | expect(request.requestHeaders['content-type']).toBe('application/json'); 59 | 60 | return [ 61 | 200, 62 | {'Content-Type': 'application/json'}, 63 | JSON.stringify(exampleResponse) 64 | ]; 65 | }; 66 | server = new Pretender(function () { 67 | this.get('https://test-company.outseta.com/api/v1/crm/deals/pWrYPn9n', responseHandler); 68 | }); 69 | 70 | const response = await deals.get('pWrYPn9n', { fields: 'Uid' }); 71 | expect(response.Name).toBe('Test Deal 1'); 72 | }); 73 | 74 | it('throws failed request', async () => { 75 | const responseHandler: ResponseHandler = (request) => { 76 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 77 | expect(request.queryParams).toEqual({ fields: Deals.DEFAULT_FIELDS }); 78 | expect(request.requestBody).toBeNull(); 79 | expect(request.requestHeaders['content-type']).toBe('application/json'); 80 | 81 | return [ 82 | 500, 83 | {'Content-Type': 'application/json'}, 84 | JSON.stringify({ "Message": "An error has occurred." }) 85 | ]; 86 | }; 87 | server = new Pretender(function () { 88 | this.get('https://test-company.outseta.com/api/v1/crm/deals/pWrYPn9n', responseHandler); 89 | }); 90 | 91 | let exception; 92 | let response; 93 | 94 | try { 95 | response = await deals.get('pWrYPn9n'); 96 | } catch (e) { 97 | exception = e; 98 | } 99 | 100 | expect(response).toBeUndefined(); 101 | expect(exception.status).toBe(500); 102 | }); 103 | }); 104 | }); 105 | }); 106 | }); 107 | 108 | const exampleResponse = { 109 | "Name": "Test Deal 1", 110 | "Amount": 100000.00, 111 | "DueDate": "2021-02-15T08:00:00", 112 | "AssignedToPersonClientIdentifier": "Rm8rb7y9", 113 | "Weight": 0, 114 | "DealPipelineStage": null, 115 | "Account": null, 116 | "DealPeople": null, 117 | "Contacts": "hello@tiltcamp.com", 118 | "Owner": null, 119 | "Uid": "Rm8pvym4", 120 | "Created": "2021-02-15T20:59:07", 121 | "Updated": "2021-02-15T20:59:07" 122 | }; 123 | -------------------------------------------------------------------------------- /test/api/crm/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { Crm } from '../../../src/api/crm'; 2 | import { Activities } from '../../../src/api/crm/activities'; 3 | import { Deals } from '../../../src/api/crm/deals'; 4 | import { People } from '../../../src/api/crm/people'; 5 | import { Accounts } from '../../../src/api/crm/accounts'; 6 | import { Store } from '../../../src/util/store'; 7 | import { ServerCredentials, UserCredentials } from '../../../src/util/credentials'; 8 | 9 | describe('api', () => 10 | describe('Crm', () => 11 | describe('constructor', () => { 12 | it('creates successfully', () => { 13 | const crm = new Crm( 14 | new Store( 15 | 'https://test-company.outseta.com/api/', 16 | new UserCredentials(), 17 | new ServerCredentials() 18 | ) 19 | ); 20 | 21 | expect(crm).toBeInstanceOf(Crm); 22 | expect(crm.accounts).toBeInstanceOf(Accounts); 23 | expect(crm.activities).toBeInstanceOf(Activities); 24 | expect(crm.deals).toBeInstanceOf(Deals); 25 | expect(crm.people).toBeInstanceOf(People); 26 | }); 27 | }) 28 | ) 29 | ); 30 | -------------------------------------------------------------------------------- /test/api/crm/people/add.spec.ts: -------------------------------------------------------------------------------- 1 | import Pretender, { ResponseHandler } from 'pretender'; 2 | import { Store } from '../../../../src/util/store'; 3 | import { People } from '../../../../src/api/crm/people'; 4 | import { ServerCredentials, UserCredentials } from '../../../../src/util/credentials'; 5 | import { Person } from '../../../../src/models/crm/person'; 6 | import { ValidationError } from '../../../../src/models/wrappers/validation-error'; 7 | 8 | describe('api', () => { 9 | describe('Crm', () => { 10 | describe('People', () => { 11 | describe('add', () => { 12 | let server: Pretender; 13 | let store: Store; 14 | 15 | let people: People; 16 | 17 | beforeEach(() => { 18 | if (server) server.shutdown(); 19 | 20 | store = new Store( 21 | 'https://test-company.outseta.com/api/v1/', 22 | new UserCredentials(), 23 | new ServerCredentials('example_key', 'example_secret') 24 | ); 25 | 26 | people = new People(store); 27 | }); 28 | 29 | afterAll(() => { 30 | server.shutdown(); 31 | }); 32 | 33 | it('handles successful request', async () => { 34 | const responseHandler: ResponseHandler = (request) => { 35 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 36 | expect(request.queryParams).toEqual({}); 37 | expect(JSON.parse(request.requestBody)).toEqual({ 38 | Email: 'hello@tiltcamp.com' 39 | }); 40 | expect(request.requestHeaders['content-type']).toBe('application/json'); 41 | 42 | return [ 43 | 200, 44 | {'Content-Type': 'application/json'}, 45 | JSON.stringify(exampleResponse) 46 | ]; 47 | }; 48 | server = new Pretender(function () { 49 | this.post('https://test-company.outseta.com/api/v1/crm/people', responseHandler); 50 | }); 51 | 52 | const response = await people.add({ 53 | Email: 'hello@tiltcamp.com' 54 | }) as Person; 55 | 56 | expect(response.Email).toBe('hello@tiltcamp.com'); 57 | }); 58 | 59 | it('handles validation errors', async () => { 60 | const responseHandler: ResponseHandler = (request) => { 61 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 62 | expect(request.queryParams).toEqual({}); 63 | expect(JSON.parse(request.requestBody)).toEqual({ 64 | Email: '' 65 | }); 66 | expect(request.requestHeaders['content-type']).toBe('application/json'); 67 | 68 | return [ 69 | 400, 70 | {'Content-Type': 'application/json'}, 71 | JSON.stringify(exampleValidationResponse) 72 | ]; 73 | }; 74 | server = new Pretender(function () { 75 | this.post('https://test-company.outseta.com/api/v1/crm/people', responseHandler); 76 | }); 77 | 78 | const response = await people.add({ 79 | Email: '' 80 | }) as ValidationError; 81 | 82 | expect(response.EntityValidationErrors[0].ValidationErrors[0]).toEqual({ 83 | ErrorCode: "outsetaError", 84 | ErrorMessage: "The Email field is not a valid e-mail address.", 85 | PropertyName: "Email" 86 | }); 87 | }); 88 | 89 | it('throws failed request', async () => { 90 | const responseHandler: ResponseHandler = (request) => { 91 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 92 | expect(request.queryParams).toEqual({}); 93 | expect(JSON.parse(request.requestBody)).toEqual({ 94 | Email: 'hello@tiltcamp.com' 95 | }); 96 | expect(request.requestHeaders['content-type']).toBe('application/json'); 97 | 98 | return [ 99 | 500, 100 | {'Content-Type': 'application/json'}, 101 | JSON.stringify({ "Message": "An error has occurred." }) 102 | ]; 103 | }; 104 | server = new Pretender(function () { 105 | this.post('https://test-company.outseta.com/api/v1/crm/people', responseHandler); 106 | }); 107 | 108 | let exception; 109 | let response; 110 | 111 | try { 112 | response = await people.add({ 113 | Email: 'hello@tiltcamp.com' 114 | }); 115 | } catch (e) { 116 | exception = e; 117 | } 118 | 119 | expect(response).toBeUndefined(); 120 | expect(exception.status).toBe(500); 121 | }); 122 | }); 123 | }); 124 | }); 125 | }); 126 | 127 | const exampleResponse = { 128 | "Email": "hello@tiltcamp.com", 129 | "FirstName": "", 130 | "LastName": "", 131 | "MailingAddress": null, 132 | "PasswordMustChange": false, 133 | "PhoneMobile": "", 134 | "PhoneWork": "", 135 | "Title": null, 136 | "Timezone": null, 137 | "Language": null, 138 | "IPAddress": null, 139 | "Referer": null, 140 | "UserAgent": null, 141 | "LastLoginDateTime": null, 142 | "OAuthGoogleProfileId": null, 143 | "PersonAccount": [], 144 | "DealPeople": [], 145 | "Account": null, 146 | "FullName": "hello@tiltcamp.com", 147 | "OAuthIntegrationStatus": 0, 148 | "UserAgentPlatformBrowser": "", 149 | "Uid": "rmkxpA1Q", 150 | "Created": "2021-01-24T23:03:35.2226324Z", 151 | "Updated": "2021-01-24T23:03:35.2226324Z" 152 | }; 153 | 154 | const exampleValidationResponse = { 155 | "ErrorMessage": "Validation failed for one or more entities. See 'EntityValidationErrors' property for more details.", 156 | "EntityValidationErrors": [ 157 | { 158 | "Entity": { 159 | "Email": "", 160 | "FirstName": "", 161 | "LastName": "", 162 | "MailingAddress": null, 163 | "PasswordMustChange": false, 164 | "PhoneMobile": "", 165 | "PhoneWork": "", 166 | "Title": null, 167 | "Timezone": null, 168 | "Language": null, 169 | "IPAddress": null, 170 | "Referer": null, 171 | "UserAgent": null, 172 | "LastLoginDateTime": null, 173 | "OAuthGoogleProfileId": null, 174 | "PersonAccount": null, 175 | "DealPeople": null, 176 | "Account": null, 177 | "FullName": "", 178 | "OAuthIntegrationStatus": 0, 179 | "UserAgentPlatformBrowser": "", 180 | "Uid": null, 181 | "Created": "2021-01-24T23:00:08.7233628Z", 182 | "Updated": "2021-01-24T23:00:08.7233628Z" 183 | }, 184 | "TypeName": "Person", 185 | "ValidationErrors": [ 186 | { 187 | "ErrorCode": "outsetaError", 188 | "ErrorMessage": "The Email field is not a valid e-mail address.", 189 | "PropertyName": "Email" 190 | } 191 | ] 192 | } 193 | ] 194 | }; 195 | -------------------------------------------------------------------------------- /test/api/crm/people/delete.spec.ts: -------------------------------------------------------------------------------- 1 | import Pretender, { ResponseHandler } from 'pretender'; 2 | import { Store } from '../../../../src/util/store'; 3 | import { People } from '../../../../src/api/crm/people'; 4 | import { ServerCredentials, UserCredentials } from '../../../../src/util/credentials'; 5 | 6 | describe('api', () => { 7 | describe('Crm', () => { 8 | describe('People', () => { 9 | describe('delete', () => { 10 | let server: Pretender; 11 | let store: Store; 12 | 13 | let people: People; 14 | 15 | beforeEach(() => { 16 | if (server) server.shutdown(); 17 | 18 | store = new Store( 19 | 'https://test-company.outseta.com/api/v1/', 20 | new UserCredentials(), 21 | new ServerCredentials('example_key', 'example_secret') 22 | ); 23 | 24 | people = new People(store); 25 | }); 26 | 27 | afterAll(() => { 28 | server.shutdown(); 29 | }); 30 | 31 | it('handles successful request', async () => { 32 | const responseHandler: ResponseHandler = (request) => { 33 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 34 | expect(request.queryParams).toEqual({}); 35 | expect(request.requestBody).toBeNull(); 36 | expect(request.requestHeaders['content-type']).toBe('application/json'); 37 | 38 | return [ 39 | 200, 40 | {'Content-Type': 'application/json'}, 41 | JSON.stringify(exampleResponse) 42 | ]; 43 | }; 44 | server = new Pretender(function () { 45 | this.delete('https://test-company.outseta.com/api/v1/crm/people/DQ2DyknW', responseHandler); 46 | }); 47 | 48 | const response = await people.delete('DQ2DyknW'); 49 | expect(response).toBeNull(); 50 | }); 51 | 52 | it('throws failed request', async () => { 53 | const responseHandler: ResponseHandler = (request) => { 54 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 55 | expect(request.queryParams).toEqual({}); 56 | expect(request.requestBody).toBeNull(); 57 | expect(request.requestHeaders['content-type']).toBe('application/json'); 58 | 59 | return [ 60 | 405, 61 | {'Content-Type': 'application/json'}, 62 | JSON.stringify(exampleErrorResponse) 63 | ]; 64 | }; 65 | server = new Pretender(function () { 66 | this.delete('https://test-company.outseta.com/api/v1/crm/people/DQ2DyknW', responseHandler); 67 | }); 68 | 69 | let exception; 70 | let response; 71 | 72 | try { 73 | response = await people.delete('DQ2DyknW'); 74 | } catch (e) { 75 | exception = e; 76 | } 77 | 78 | expect(response).toBeUndefined(); 79 | expect(exception.status).toBe(405); 80 | }); 81 | }); 82 | }); 83 | }); 84 | }); 85 | 86 | const exampleResponse = ''; 87 | 88 | const exampleErrorResponse = { 89 | "ErrorMessage": "Could not convert hash rmkxpB1Q to long", 90 | "EntityValidationErrors": [] 91 | }; 92 | -------------------------------------------------------------------------------- /test/api/crm/people/get-all.spec.ts: -------------------------------------------------------------------------------- 1 | import Pretender, { ResponseHandler } from 'pretender'; 2 | import { Store } from '../../../../src/util/store'; 3 | import { People } from '../../../../src/api/crm/people'; 4 | import { ServerCredentials, UserCredentials } from '../../../../src/util/credentials'; 5 | 6 | describe('api', () => { 7 | describe('Crm', () => { 8 | describe('People', () => { 9 | describe('getAll', () => { 10 | let server: Pretender; 11 | let store: Store; 12 | 13 | let people: People; 14 | 15 | beforeEach(() => { 16 | if (server) server.shutdown(); 17 | 18 | store = new Store( 19 | 'https://test-company.outseta.com/api/v1/', 20 | new UserCredentials(), 21 | new ServerCredentials('example_key', 'example_secret') 22 | ); 23 | 24 | people = new People(store); 25 | }); 26 | 27 | afterAll(() => { 28 | server.shutdown(); 29 | }); 30 | 31 | it('handles successful request', async () => { 32 | const responseHandler: ResponseHandler = (request) => { 33 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 34 | expect(request.queryParams).toEqual({}); 35 | expect(request.requestBody).toBeNull(); 36 | expect(request.requestHeaders['content-type']).toBe('application/json'); 37 | 38 | return [ 39 | 200, 40 | {'Content-Type': 'application/json'}, 41 | JSON.stringify(exampleResponse) 42 | ]; 43 | }; 44 | server = new Pretender(function () { 45 | this.get('https://test-company.outseta.com/api/v1/crm/people', responseHandler); 46 | }); 47 | 48 | const response = await people.getAll(); 49 | expect(response.metadata.total).toBe(1); 50 | expect(response.items).toHaveSize(1); 51 | expect(response.items[0].Email).toBe('demo@tiltcamp.com'); 52 | }); 53 | 54 | it('handles request with pagination', async () => { 55 | const responseHandler: ResponseHandler = (request) => { 56 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 57 | expect(request.queryParams).toEqual({ offset: '10', limit: '20' }); 58 | expect(request.requestBody).toBeNull(); 59 | expect(request.requestHeaders['content-type']).toBe('application/json'); 60 | 61 | return [ 62 | 200, 63 | {'Content-Type': 'application/json'}, 64 | JSON.stringify({}) 65 | ]; 66 | }; 67 | server = new Pretender(function () { 68 | this.get('https://test-company.outseta.com/api/v1/crm/people', responseHandler); 69 | }); 70 | 71 | await people.getAll({ offset: 10, limit: 20 }); 72 | }); 73 | 74 | it('throws failed request', async () => { 75 | const responseHandler: ResponseHandler = (request) => { 76 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 77 | expect(request.queryParams).toEqual({}); 78 | expect(request.requestBody).toBeNull(); 79 | expect(request.requestHeaders['content-type']).toBe('application/json'); 80 | 81 | return [ 82 | 500, 83 | {'Content-Type': 'application/json'}, 84 | JSON.stringify({ "Message": "An error has occurred." }) 85 | ]; 86 | }; 87 | server = new Pretender(function () { 88 | this.get('https://test-company.outseta.com/api/v1/crm/people', responseHandler); 89 | }); 90 | 91 | let exception; 92 | let response; 93 | 94 | try { 95 | response = await people.getAll(); 96 | } catch (e) { 97 | exception = e; 98 | } 99 | 100 | expect(response).toBeUndefined(); 101 | expect(exception.status).toBe(500); 102 | }); 103 | }); 104 | }); 105 | }); 106 | }); 107 | 108 | const exampleResponse = { 109 | "metadata": { 110 | "limit": 100, 111 | "offset": 0, 112 | "total": 1 113 | }, 114 | "items": [ 115 | { 116 | "Email": "demo@tiltcamp.com", 117 | "FirstName": "Jane", 118 | "LastName": "Doe", 119 | "MailingAddress": { 120 | "AddressLine1": "152 Test Lane", 121 | "AddressLine2": "Apt L", 122 | "AddressLine3": null, 123 | "City": "San Diego", 124 | "State": "CA", 125 | "PostalCode": "91511", 126 | "Country": "United States of America", 127 | "Uid": "yW1Kj1QB", 128 | "Created": "2021-01-20T05:24:53", 129 | "Updated": "2021-01-24T19:05:56" 130 | }, 131 | "PasswordMustChange": false, 132 | "PhoneMobile": "4084841547", 133 | "PhoneWork": "4122144785", 134 | "Title": "Engineer", 135 | "Timezone": null, 136 | "Language": null, 137 | "IPAddress": null, 138 | "Referer": null, 139 | "UserAgent": null, 140 | "LastLoginDateTime": null, 141 | "OAuthGoogleProfileId": null, 142 | "PersonAccount": [ 143 | { 144 | "Person": null, 145 | "Account": null, 146 | "IsPrimary": true, 147 | "Uid": "496L7AmX", 148 | "Created": "2021-01-20T05:25:56", 149 | "Updated": "2021-01-20T05:25:56" 150 | } 151 | ], 152 | "DealPeople": [], 153 | "Account": null, 154 | "FullName": "Jane Doe", 155 | "OAuthIntegrationStatus": 0, 156 | "UserAgentPlatformBrowser": "", 157 | "Uid": "DQ2DyknW", 158 | "Created": "2021-01-20T05:24:53", 159 | "Updated": "2021-01-24T19:16:37" 160 | } 161 | ] 162 | }; 163 | -------------------------------------------------------------------------------- /test/api/crm/people/get.spec.ts: -------------------------------------------------------------------------------- 1 | import Pretender, { ResponseHandler } from 'pretender'; 2 | import { Store } from '../../../../src/util/store'; 3 | import { People } from '../../../../src/api/crm/people'; 4 | import { ServerCredentials, UserCredentials } from '../../../../src/util/credentials'; 5 | 6 | describe('api', () => { 7 | describe('Crm', () => { 8 | describe('People', () => { 9 | describe('get', () => { 10 | let server: Pretender; 11 | let store: Store; 12 | 13 | let people: People; 14 | 15 | beforeEach(() => { 16 | if (server) server.shutdown(); 17 | 18 | store = new Store( 19 | 'https://test-company.outseta.com/api/v1/', 20 | new UserCredentials(), 21 | new ServerCredentials('example_key', 'example_secret') 22 | ); 23 | 24 | people = new People(store); 25 | }); 26 | 27 | afterAll(() => { 28 | server.shutdown(); 29 | }); 30 | 31 | it('handles successful request', async () => { 32 | const responseHandler: ResponseHandler = (request) => { 33 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 34 | expect(request.queryParams).toEqual({}); 35 | expect(request.requestBody).toBeNull(); 36 | expect(request.requestHeaders['content-type']).toBe('application/json'); 37 | 38 | return [ 39 | 200, 40 | {'Content-Type': 'application/json'}, 41 | JSON.stringify(exampleResponse) 42 | ]; 43 | }; 44 | server = new Pretender(function () { 45 | this.get('https://test-company.outseta.com/api/v1/crm/people/DQ2DyknW', responseHandler); 46 | }); 47 | 48 | const response = await people.get('DQ2DyknW'); 49 | expect(response.Email).toBe('demo@tiltcamp.com'); 50 | }); 51 | 52 | it('throws failed request', async () => { 53 | const responseHandler: ResponseHandler = (request) => { 54 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 55 | expect(request.queryParams).toEqual({}); 56 | expect(request.requestBody).toBeNull(); 57 | expect(request.requestHeaders['content-type']).toBe('application/json'); 58 | 59 | return [ 60 | 500, 61 | {'Content-Type': 'application/json'}, 62 | JSON.stringify({ "Message": "An error has occurred." }) 63 | ]; 64 | }; 65 | server = new Pretender(function () { 66 | this.get('https://test-company.outseta.com/api/v1/crm/people/DQ2DyknW', responseHandler); 67 | }); 68 | 69 | let exception; 70 | let response; 71 | 72 | try { 73 | response = await people.get('DQ2DyknW'); 74 | } catch (e) { 75 | exception = e; 76 | } 77 | 78 | expect(response).toBeUndefined(); 79 | expect(exception.status).toBe(500); 80 | }); 81 | }); 82 | }); 83 | }); 84 | }); 85 | 86 | const exampleResponse = { 87 | "Email": "demo@tiltcamp.com", 88 | "FirstName": "Jane", 89 | "LastName": "Doe", 90 | "MailingAddress": { 91 | "AddressLine1": "152 Test Lane", 92 | "AddressLine2": "Apt L", 93 | "AddressLine3": null, 94 | "City": "San Diego", 95 | "State": "CA", 96 | "PostalCode": "91511", 97 | "Country": "United States of America", 98 | "Uid": "yW1Kj1QB", 99 | "Created": "2021-01-20T05:24:53", 100 | "Updated": "2021-01-24T19:05:56" 101 | }, 102 | "PasswordMustChange": false, 103 | "PhoneMobile": "4084841547", 104 | "PhoneWork": "4122144785", 105 | "ProfileImageS3Url": "https://s3.amazonaws.com/outseta-production/0000/user-image.jpg", 106 | "Title": "Engineer", 107 | "Timezone": null, 108 | "Language": null, 109 | "IPAddress": null, 110 | "Referer": null, 111 | "UserAgent": null, 112 | "LastLoginDateTime": null, 113 | "OAuthGoogleProfileId": null, 114 | "PersonAccount": [ 115 | { 116 | "Person": null, 117 | "Account": null, 118 | "IsPrimary": true, 119 | "Uid": "496L7AmX", 120 | "Created": "2021-01-20T05:25:56", 121 | "Updated": "2021-01-20T05:25:56" 122 | } 123 | ], 124 | "DealPeople": [], 125 | "Account": null, 126 | "FullName": "Jane Doe", 127 | "OAuthIntegrationStatus": 0, 128 | "UserAgentPlatformBrowser": "", 129 | "Uid": "DQ2DyknW", 130 | "Created": "2021-01-20T05:24:53", 131 | "Updated": "2021-01-24T19:16:37" 132 | }; 133 | -------------------------------------------------------------------------------- /test/api/marketing/email-list-subscriptions/delete.spec.ts: -------------------------------------------------------------------------------- 1 | import Pretender, { ResponseHandler } from 'pretender'; 2 | import { EmailListSubscriptions } from '../../../../src/api/marketing/email-list-subscriptions'; 3 | import { Store } from '../../../../src/util/store'; 4 | 5 | import { ServerCredentials, UserCredentials } from '../../../../src/util/credentials'; 6 | 7 | describe('api', () => { 8 | describe('Marketing', () => { 9 | describe('EmailListSubscriptions', () => { 10 | describe('delete', () => { 11 | let server: Pretender; 12 | let store: Store; 13 | 14 | let emailListSubscriptions: EmailListSubscriptions; 15 | 16 | beforeEach(() => { 17 | if (server) server.shutdown(); 18 | 19 | store = new Store( 20 | 'https://test-company.outseta.com/api/v1/', 21 | new UserCredentials(), 22 | new ServerCredentials('example_key', 'example_secret') 23 | ); 24 | 25 | emailListSubscriptions = new EmailListSubscriptions(store); 26 | }); 27 | 28 | afterAll(() => { 29 | server.shutdown(); 30 | }); 31 | 32 | it('handles successful request', async () => { 33 | const responseHandler: ResponseHandler = (request) => { 34 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 35 | expect(request.queryParams).toEqual({}); 36 | expect(request.requestBody).toBeNull(); 37 | expect(request.requestHeaders['content-type']).toBe('application/json'); 38 | 39 | return [ 40 | 200, 41 | {'Content-Type': 'application/json'}, 42 | '' 43 | ]; 44 | }; 45 | server = new Pretender(function () { 46 | this.delete('https://test-company.outseta.com/api/v1/email/lists/ngWKYnQp/subscriptions/wQXrBxWK', responseHandler); 47 | }); 48 | 49 | const response = await emailListSubscriptions.delete({ 50 | EmailList: { 51 | Uid: 'ngWKYnQp' 52 | }, 53 | Person: { 54 | Uid: 'wQXrBxWK' 55 | } 56 | }); 57 | expect(response).toBeNull(); 58 | }); 59 | 60 | it('throws failed request', async () => { 61 | const responseHandler: ResponseHandler = (request) => { 62 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 63 | expect(request.queryParams).toEqual({}); 64 | expect(request.requestBody).toBeNull(); 65 | expect(request.requestHeaders['content-type']).toBe('application/json'); 66 | 67 | return [ 68 | 404, 69 | {'Content-Type': 'application/json'}, 70 | '' 71 | ]; 72 | }; 73 | server = new Pretender(function () { 74 | this.delete('https://test-company.outseta.com/api/v1/email/lists/ngWKYnQp/subscriptions/wQXrBxWK', responseHandler); 75 | }); 76 | 77 | let exception; 78 | let response; 79 | 80 | try { 81 | response = await emailListSubscriptions.delete({ 82 | EmailList: { 83 | Uid: 'ngWKYnQp' 84 | }, 85 | Person: { 86 | Uid: 'wQXrBxWK' 87 | } 88 | }); 89 | } catch (e) { 90 | exception = e; 91 | } 92 | 93 | expect(response).toBeUndefined(); 94 | expect(exception.status).toBe(404); 95 | }); 96 | }); 97 | }); 98 | }); 99 | }); 100 | -------------------------------------------------------------------------------- /test/api/marketing/email-list-subscriptions/get-all.spec.ts: -------------------------------------------------------------------------------- 1 | import Pretender, { ResponseHandler } from 'pretender'; 2 | import { EmailListSubscriptions } from '../../../../src/api/marketing/email-list-subscriptions'; 3 | import { Store } from '../../../../src/util/store'; 4 | 5 | import { ServerCredentials, UserCredentials } from '../../../../src/util/credentials'; 6 | 7 | describe('api', () => { 8 | describe('Marketing', () => { 9 | describe('EmailListSubscriptions', () => { 10 | describe('getAll', () => { 11 | let server: Pretender; 12 | let store: Store; 13 | 14 | let emailListSubscriptions: EmailListSubscriptions; 15 | 16 | beforeEach(() => { 17 | if (server) server.shutdown(); 18 | 19 | store = new Store( 20 | 'https://test-company.outseta.com/api/v1/', 21 | new UserCredentials(), 22 | new ServerCredentials('example_key', 'example_secret') 23 | ); 24 | 25 | emailListSubscriptions = new EmailListSubscriptions(store); 26 | }); 27 | 28 | afterAll(() => { 29 | server.shutdown(); 30 | }); 31 | 32 | it('handles successful request', async () => { 33 | const responseHandler: ResponseHandler = (request) => { 34 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 35 | expect(request.queryParams).toEqual({ 36 | fields: EmailListSubscriptions.DEFAULT_FIELDS, 37 | orderBy: 'SubscribedDate' 38 | }); 39 | expect(request.requestBody).toBeNull(); 40 | expect(request.requestHeaders['content-type']).toBe('application/json'); 41 | 42 | return [ 43 | 200, 44 | {'Content-Type': 'application/json'}, 45 | JSON.stringify(exampleResponse) 46 | ]; 47 | }; 48 | server = new Pretender(function () { 49 | this.get('https://test-company.outseta.com/api/v1/email/lists/ngWKYnQp/subscriptions', responseHandler); 50 | }); 51 | 52 | const response = await emailListSubscriptions.getAll('ngWKYnQp'); 53 | expect(response.metadata.total).toBe(1); 54 | expect(response.items).toHaveSize(1); 55 | expect(response.items[0].Uid).toBe('wQXrBxWK'); 56 | }); 57 | 58 | it('handles request with pagination, filters, fields', async () => { 59 | const responseHandler: ResponseHandler = (request) => { 60 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 61 | expect(request.queryParams).toEqual({ 62 | offset: '10', 63 | limit: '20', 64 | fields: 'Uid', 65 | orderBy: 'SubscribedDate' 66 | }); 67 | expect(request.requestBody).toBeNull(); 68 | expect(request.requestHeaders['content-type']).toBe('application/json'); 69 | 70 | return [ 71 | 200, 72 | {'Content-Type': 'application/json'}, 73 | JSON.stringify(exampleNoResults) 74 | ]; 75 | }; 76 | server = new Pretender(function () { 77 | this.get('https://test-company.outseta.com/api/v1/email/lists/alWKXnQp/subscriptions', responseHandler); 78 | }); 79 | 80 | const response = await emailListSubscriptions.getAll('alWKXnQp', { 81 | offset: 10, 82 | limit: 20, 83 | fields: 'Uid' 84 | }); 85 | expect(response.metadata.total).toBe(0); 86 | expect(response.items).toHaveSize(0); 87 | }); 88 | 89 | it('throws failed request', async () => { 90 | const responseHandler: ResponseHandler = (request) => { 91 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 92 | expect(request.queryParams).toEqual({ 93 | fields: EmailListSubscriptions.DEFAULT_FIELDS, 94 | orderBy: 'SubscribedDate' 95 | }); 96 | expect(request.requestBody).toBeNull(); 97 | expect(request.requestHeaders['content-type']).toBe('application/json'); 98 | 99 | return [ 100 | 500, 101 | {'Content-Type': 'application/json'}, 102 | JSON.stringify({ "Message": "An error has occurred." }) 103 | ]; 104 | }; 105 | server = new Pretender(function () { 106 | this.get('https://test-company.outseta.com/api/v1/email/lists/malformed/subscriptions', responseHandler); 107 | }); 108 | 109 | let exception; 110 | let response; 111 | 112 | try { 113 | response = await emailListSubscriptions.getAll('malformed'); 114 | } catch (e) { 115 | exception = e; 116 | } 117 | 118 | expect(response).toBeUndefined(); 119 | expect(exception.status).toBe(500); 120 | }); 121 | }); 122 | }); 123 | }); 124 | }); 125 | 126 | const exampleResponse = { 127 | "metadata": { 128 | "limit": 100, 129 | "offset": 0, 130 | "total": 1 131 | }, 132 | "items": [ 133 | { 134 | "EmailList": { 135 | "Name": "Test Email List 1", 136 | "WelcomeSubject": null, 137 | "WelcomeBody": "Welcome", 138 | "WelcomeFromName": null, 139 | "WelcomeFromEmail": null, 140 | "EmailListPerson": null, 141 | "CountSubscriptionsActive": 0, 142 | "CountSubscriptionsBounce": 0, 143 | "CountSubscriptionsSpam": 0, 144 | "CountSubscriptionsUnsubscribed": 0, 145 | "Uid": "ngWKYnQp", 146 | "Created": "2021-02-16T00:51:31", 147 | "Updated": "2021-02-16T00:51:31" 148 | }, 149 | "Person": { 150 | "Email": "hello@tiltcamp.com", 151 | "FirstName": "", 152 | "LastName": "", 153 | "MailingAddress": null, 154 | "PasswordMustChange": false, 155 | "PhoneMobile": "", 156 | "PhoneWork": "", 157 | "Title": null, 158 | "Timezone": null, 159 | "Language": null, 160 | "IPAddress": null, 161 | "Referer": null, 162 | "UserAgent": null, 163 | "LastLoginDateTime": null, 164 | "OAuthGoogleProfileId": null, 165 | "PersonAccount": null, 166 | "DealPeople": null, 167 | "Account": null, 168 | "FullName": "hello@tiltcamp.com", 169 | "OAuthIntegrationStatus": 0, 170 | "UserAgentPlatformBrowser": "", 171 | "Uid": "dQGn2ozm", 172 | "Created": "2021-02-16T02:14:37", 173 | "Updated": "2021-02-16T02:14:37" 174 | }, 175 | "EmailListSubscriberStatus": 1, 176 | "SubscribedDate": "2021-02-16T02:15:10", 177 | "ConfirmedDate": null, 178 | "UnsubscribedDate": null, 179 | "CleanedDate": null, 180 | "WelcomeEmailDeliverDateTime": null, 181 | "WelcomeEmailOpenDateTime": null, 182 | "UnsubscribeReason": null, 183 | "UnsubscribeReasonOther": null, 184 | "RecaptchaToken": null, 185 | "RecaptchaSiteKey": null, 186 | "SendWelcomeEmail": false, 187 | "Source": null, 188 | "Uid": "wQXrBxWK", 189 | "Created": "2021-02-16T02:15:10", 190 | "Updated": "2021-02-16T02:15:10" 191 | } 192 | ] 193 | }; 194 | 195 | const exampleNoResults = { 196 | "metadata": { 197 | "limit": 100, 198 | "offset": 0, 199 | "total": 0 200 | }, 201 | "items": [] 202 | }; 203 | -------------------------------------------------------------------------------- /test/api/marketing/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { Marketing } from '../../../src/api/marketing'; 2 | import { EmailListSubscriptions } from '../../../src/api/marketing/email-list-subscriptions'; 3 | import { ServerCredentials, UserCredentials } from '../../../src/util/credentials'; 4 | import { Store } from '../../../src/util/store'; 5 | 6 | describe('api', () => 7 | describe('Marketing', () => 8 | describe('constructor', () => { 9 | it('creates successfully', () => { 10 | const marketing = new Marketing( 11 | new Store( 12 | 'https://test-company.outseta.com/api/', 13 | new UserCredentials(), 14 | new ServerCredentials() 15 | ) 16 | ); 17 | 18 | expect(marketing).toBeInstanceOf(Marketing); 19 | expect(marketing.emailListSubscriptions).toBeInstanceOf(EmailListSubscriptions); 20 | }); 21 | }) 22 | ) 23 | ); 24 | -------------------------------------------------------------------------------- /test/api/support/cases/add-reply-from-agent.spec.ts: -------------------------------------------------------------------------------- 1 | import Pretender, { ResponseHandler } from 'pretender'; 2 | import { Cases } from '../../../../src/api/support/cases'; 3 | import { ServerCredentials, UserCredentials } from '../../../../src/util/credentials'; 4 | import { Store } from '../../../../src/util/store'; 5 | 6 | describe('api', () => { 7 | describe('Support', () => { 8 | describe('Cases', () => { 9 | describe('addReplyFromAgent', () => { 10 | let server: Pretender; 11 | let store: Store; 12 | 13 | let cases: Cases; 14 | 15 | beforeEach(() => { 16 | if (server) server.shutdown(); 17 | 18 | store = new Store( 19 | 'https://test-company.outseta.com/api/v1/', 20 | new UserCredentials(), 21 | new ServerCredentials('example_key', 'example_secret') 22 | ); 23 | 24 | cases = new Cases(store); 25 | }); 26 | 27 | afterAll(() => { 28 | server.shutdown(); 29 | }); 30 | 31 | it('handles successful request', async () => { 32 | const responseHandler: ResponseHandler = (request) => { 33 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 34 | expect(request.queryParams).toEqual({}); 35 | expect(JSON.parse(request.requestBody)).toEqual({ 36 | AgentName: 'Firstname Lastname', 37 | Case: { 38 | Uid: 'rmkyza9g' 39 | }, 40 | Comment: 'This is a response comment' 41 | }); 42 | expect(request.requestHeaders['content-type']).toBe('application/json'); 43 | 44 | return [ 45 | 200, 46 | {'Content-Type': 'application/json'}, 47 | '' 48 | ]; 49 | }; 50 | server = new Pretender(function () { 51 | this.post('https://test-company.outseta.com/api/v1/support/cases/rmkyza9g/replies', responseHandler); 52 | }); 53 | 54 | const response = await cases.addReplyFromAgent({ 55 | AgentName: 'Firstname Lastname', 56 | Case: { 57 | Uid: 'rmkyza9g' 58 | }, 59 | Comment: 'This is a response comment' 60 | }); 61 | 62 | expect(response).toBeNull(); 63 | }); 64 | 65 | it('throws failed request', async () => { 66 | const responseHandler: ResponseHandler = (request) => { 67 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 68 | expect(request.queryParams).toEqual({}); 69 | expect(JSON.parse(request.requestBody)).toEqual({ 70 | AgentName: 'Firstname Lastname', 71 | Case: { 72 | Uid: 'rmkyza9g' 73 | }, 74 | Comment: 'This is a response comment' 75 | }); 76 | expect(request.requestHeaders['content-type']).toBe('application/json'); 77 | 78 | return [ 79 | 500, 80 | {'Content-Type': 'application/json'}, 81 | JSON.stringify({ "Message": "An error has occurred." }) 82 | ]; 83 | }; 84 | server = new Pretender(function () { 85 | this.post('https://test-company.outseta.com/api/v1/support/cases/rmkyza9g/replies', responseHandler); 86 | }); 87 | 88 | let exception; 89 | let response; 90 | 91 | try { 92 | response = await cases.addReplyFromAgent({ 93 | AgentName: 'Firstname Lastname', 94 | Case: { 95 | Uid: 'rmkyza9g' 96 | }, 97 | Comment: 'This is a response comment' 98 | }); 99 | } catch (e) { 100 | exception = e; 101 | } 102 | 103 | expect(response).toBeUndefined(); 104 | expect(exception.status).toBe(500); 105 | }); 106 | }); 107 | }); 108 | }); 109 | }); 110 | -------------------------------------------------------------------------------- /test/api/support/cases/add-reply-from-client.spec.ts: -------------------------------------------------------------------------------- 1 | import Pretender, { ResponseHandler } from 'pretender'; 2 | import { Cases } from '../../../../src/api/support/cases'; 3 | import { CaseHistory } from '../../../../src/models/support/case-history'; 4 | import { ServerCredentials, UserCredentials } from '../../../../src/util/credentials'; 5 | import { Store } from '../../../../src/util/store'; 6 | 7 | describe('api', () => { 8 | describe('Support', () => { 9 | describe('Cases', () => { 10 | describe('addReplyFromClient', () => { 11 | let server: Pretender; 12 | let store: Store; 13 | 14 | let cases: Cases; 15 | 16 | beforeEach(() => { 17 | if (server) server.shutdown(); 18 | 19 | store = new Store( 20 | 'https://test-company.outseta.com/api/v1/', 21 | new UserCredentials(), 22 | new ServerCredentials('example_key', 'example_secret') 23 | ); 24 | 25 | cases = new Cases(store); 26 | }); 27 | 28 | afterAll(() => { 29 | server.shutdown(); 30 | }); 31 | 32 | it('handles successful request', async () => { 33 | const responseHandler: ResponseHandler = (request) => { 34 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 35 | expect(request.queryParams).toEqual({}); 36 | expect(JSON.parse(request.requestBody)).toBeNull(); 37 | expect(request.requestHeaders['content-type']).toBe('application/json'); 38 | 39 | return [ 40 | 200, 41 | {'Content-Type': 'application/json'}, 42 | JSON.stringify(exampleResponse) 43 | ]; 44 | }; 45 | server = new Pretender(function () { 46 | this.post( 47 | 'https://test-company.outseta.com/api/v1/support/cases/rmkyza9g/clientresponse/This%20is%20a%20message%20from%20a%20client', 48 | responseHandler 49 | ); 50 | }); 51 | 52 | const response = await cases.addReplyFromClient({ 53 | Case: { 54 | Uid: 'rmkyza9g' 55 | }, 56 | Comment: 'This is a message from a client' 57 | }) as CaseHistory; 58 | 59 | expect(response.Comment).toBe('This is a message from a client'); 60 | }); 61 | 62 | it('throws failed request', async () => { 63 | const responseHandler: ResponseHandler = (request) => { 64 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 65 | expect(request.queryParams).toEqual({}); 66 | expect(JSON.parse(request.requestBody)).toBeNull(); 67 | expect(request.requestHeaders['content-type']).toBe('application/json'); 68 | 69 | return [ 70 | 500, 71 | {'Content-Type': 'application/json'}, 72 | JSON.stringify({ "Message": "An error has occurred." }) 73 | ]; 74 | }; 75 | server = new Pretender(function () { 76 | this.post( 77 | 'https://test-company.outseta.com/api/v1/support/cases/rmkyza9g/clientresponse/This%20is%20a%20message%20from%20a%20client', 78 | responseHandler 79 | ); 80 | }); 81 | 82 | let exception; 83 | let response; 84 | 85 | try { 86 | response = await cases.addReplyFromClient({ 87 | Case: { 88 | Uid: 'rmkyza9g' 89 | }, 90 | Comment: 'This is a message from a client' 91 | }); 92 | } catch (e) { 93 | exception = e; 94 | } 95 | 96 | expect(response).toBeUndefined(); 97 | expect(exception.status).toBe(500); 98 | }); 99 | }); 100 | }); 101 | }); 102 | }); 103 | 104 | const exampleResponse = { 105 | "HistoryDateTime": "2021-02-16T16:33:26.8564311Z", 106 | "Case": { 107 | "SubmittedDateTime": "2021-02-16T04:50:22", 108 | "FromPerson": null, 109 | "AssignedToPersonClientIdentifier": "Rm8rb7y9", 110 | "Subject": "Test Case 1", 111 | "Body": "

This is an example case

", 112 | "UserAgent": null, 113 | "Status": 1, 114 | "Source": 1, 115 | "CaseHistories": null, 116 | "IsOnline": false, 117 | "LastCaseHistory": null, 118 | "Participants": null, 119 | "RecaptchaToken": null, 120 | "Uid": "rmkyza9g", 121 | "Created": "2021-02-16T04:50:22", 122 | "Updated": "2021-02-16T04:50:22" 123 | }, 124 | "AgentName": null, 125 | "Comment": "This is a message from a client", 126 | "Type": 1, 127 | "SeenDateTime": null, 128 | "ClickDateTime": null, 129 | "PersonEmail": null, 130 | "NewUvi": null, 131 | "Uid": "B9lb8Em8", 132 | "Created": "2021-02-16T16:33:26.8564311Z", 133 | "Updated": "2021-02-16T16:33:26.8564311Z" 134 | }; 135 | 136 | -------------------------------------------------------------------------------- /test/api/support/cases/get-all.spec.ts: -------------------------------------------------------------------------------- 1 | import Pretender, { ResponseHandler } from 'pretender'; 2 | import { Cases } from '../../../../src/api/support/cases'; 3 | import { Store } from '../../../../src/util/store'; 4 | 5 | import { ServerCredentials, UserCredentials } from '../../../../src/util/credentials'; 6 | 7 | describe('api', () => { 8 | describe('Support', () => { 9 | describe('Cases', () => { 10 | describe('getAll', () => { 11 | let server: Pretender; 12 | let store: Store; 13 | 14 | let cases: Cases; 15 | 16 | beforeEach(() => { 17 | if (server) server.shutdown(); 18 | 19 | store = new Store( 20 | 'https://test-company.outseta.com/api/v1/', 21 | new UserCredentials(), 22 | new ServerCredentials('example_key', 'example_secret') 23 | ); 24 | 25 | cases = new Cases(store); 26 | }); 27 | 28 | afterAll(() => { 29 | server.shutdown(); 30 | }); 31 | 32 | it('handles successful request', async () => { 33 | const responseHandler: ResponseHandler = (request) => { 34 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 35 | expect(request.queryParams).toEqual({ fields: Cases.DEFAULT_FIELDS }); 36 | expect(request.requestBody).toBeNull(); 37 | expect(request.requestHeaders['content-type']).toBe('application/json'); 38 | 39 | return [ 40 | 200, 41 | {'Content-Type': 'application/json'}, 42 | JSON.stringify(exampleResponse) 43 | ]; 44 | }; 45 | server = new Pretender(function () { 46 | this.get('https://test-company.outseta.com/api/v1/support/cases', responseHandler); 47 | }); 48 | 49 | const response = await cases.getAll(); 50 | expect(response.metadata.total).toBe(1); 51 | expect(response.items).toHaveSize(1); 52 | expect(response.items[0].Uid).toBe('rmkyza9g'); 53 | expect(response.items[0].Subject).toBe('Test Case 1'); 54 | }); 55 | 56 | it('handles request with pagination, filters, fields', async () => { 57 | const responseHandler: ResponseHandler = (request) => { 58 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 59 | expect(request.queryParams).toEqual({ 60 | offset: '10', 61 | limit: '20', 62 | fields: 'Uid', 63 | 'FromPerson.Email': 'hello@tiltcamp.com' 64 | }); 65 | expect(request.requestBody).toBeNull(); 66 | expect(request.requestHeaders['content-type']).toBe('application/json'); 67 | 68 | return [ 69 | 200, 70 | {'Content-Type': 'application/json'}, 71 | JSON.stringify(exampleNoResults) 72 | ]; 73 | }; 74 | server = new Pretender(function () { 75 | this.get('https://test-company.outseta.com/api/v1/support/cases', responseHandler); 76 | }); 77 | 78 | const response = await cases.getAll({ 79 | offset: 10, 80 | limit: 20, 81 | fields: 'Uid', 82 | FromPerson: { 83 | Email: 'hello@tiltcamp.com' 84 | } 85 | }); 86 | expect(response.metadata.total).toBe(0); 87 | expect(response.items).toHaveSize(0); 88 | }); 89 | 90 | it('handles request with FromPerson.Uid filter', async () => { 91 | const responseHandler: ResponseHandler = (request) => { 92 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 93 | expect(request.queryParams).toEqual({ 94 | fields: Cases.DEFAULT_FIELDS, 95 | 'FromPerson.Uid': 'DQ2DyknW' 96 | }); 97 | expect(request.requestBody).toBeNull(); 98 | expect(request.requestHeaders['content-type']).toBe('application/json'); 99 | 100 | return [ 101 | 200, 102 | {'Content-Type': 'application/json'}, 103 | JSON.stringify(exampleNoResults) 104 | ]; 105 | }; 106 | server = new Pretender(function () { 107 | this.get('https://test-company.outseta.com/api/v1/support/cases', responseHandler); 108 | }); 109 | 110 | const response = await cases.getAll({ 111 | FromPerson: { 112 | Uid: 'DQ2DyknW', 113 | Email: 'hello@tiltcamp.com' 114 | } 115 | }); 116 | expect(response.metadata.total).toBe(0); 117 | expect(response.items).toHaveSize(0); 118 | }); 119 | 120 | it('throws failed request', async () => { 121 | const responseHandler: ResponseHandler = (request) => { 122 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 123 | expect(request.queryParams).toEqual({ fields: Cases.DEFAULT_FIELDS }); 124 | expect(request.requestBody).toBeNull(); 125 | expect(request.requestHeaders['content-type']).toBe('application/json'); 126 | 127 | return [ 128 | 500, 129 | {'Content-Type': 'application/json'}, 130 | JSON.stringify({ "Message": "An error has occurred." }) 131 | ]; 132 | }; 133 | server = new Pretender(function () { 134 | this.get('https://test-company.outseta.com/api/v1/support/cases', responseHandler); 135 | }); 136 | 137 | let exception; 138 | let response; 139 | 140 | try { 141 | response = await cases.getAll(); 142 | } catch (e) { 143 | exception = e; 144 | } 145 | 146 | expect(response).toBeUndefined(); 147 | expect(exception.status).toBe(500); 148 | }); 149 | }); 150 | }); 151 | }); 152 | }); 153 | 154 | const exampleResponse = { 155 | "metadata": { 156 | "limit": 100, 157 | "offset": 0, 158 | "total": 1 159 | }, 160 | "items": [ 161 | { 162 | "SubmittedDateTime": "2021-02-16T04:50:22", 163 | "FromPerson": null, 164 | "AssignedToPersonClientIdentifier": "Rm8rb7y9", 165 | "Subject": "Test Case 1", 166 | "Body": "

This is an example case

", 167 | "UserAgent": null, 168 | "Status": 1, 169 | "Source": 1, 170 | "CaseHistories": null, 171 | "IsOnline": false, 172 | "LastCaseHistory": null, 173 | "Participants": null, 174 | "RecaptchaToken": null, 175 | "Uid": "rmkyza9g", 176 | "Created": "2021-02-16T04:50:22", 177 | "Updated": "2021-02-16T04:50:22" 178 | } 179 | ] 180 | }; 181 | 182 | const exampleNoResults = { 183 | "metadata": { 184 | "limit": 100, 185 | "offset": 0, 186 | "total": 0 187 | }, 188 | "items": [] 189 | }; 190 | -------------------------------------------------------------------------------- /test/api/support/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { Support } from '../../../src/api/support'; 2 | import { Cases } from '../../../src/api/support/cases'; 3 | import { ServerCredentials, UserCredentials } from '../../../src/util/credentials'; 4 | import { Store } from '../../../src/util/store'; 5 | 6 | describe('api', () => 7 | describe('Support', () => 8 | describe('constructor', () => { 9 | it('creates successfully', () => { 10 | const support = new Support( 11 | new Store( 12 | 'https://test-company.outseta.com/api/', 13 | new UserCredentials(), 14 | new ServerCredentials() 15 | ) 16 | ); 17 | 18 | expect(support).toBeInstanceOf(Support); 19 | expect(support.cases).toBeInstanceOf(Cases); 20 | }); 21 | }) 22 | ) 23 | ); 24 | -------------------------------------------------------------------------------- /test/api/user/impersonate.spec.ts: -------------------------------------------------------------------------------- 1 | import Pretender, { ResponseHandler } from 'pretender'; 2 | import { Store } from '../../../src/util/store'; 3 | import { User } from '../../../src/api/user'; 4 | import { ServerCredentials, UserCredentials } from '../../../src/util/credentials'; 5 | 6 | describe('api', () => { 7 | describe('User', () => { 8 | describe('impersonate', () => { 9 | let server: Pretender; 10 | let store: Store; 11 | 12 | let user: User; 13 | 14 | beforeEach(() => { 15 | if (server) server.shutdown(); 16 | 17 | store = new Store( 18 | 'https://test-company.outseta.com/api/v1/', 19 | new UserCredentials(), 20 | new ServerCredentials('example_key', 'example_secret') 21 | ); 22 | 23 | user = new User(store); 24 | }); 25 | 26 | afterAll(() => { 27 | server.shutdown(); 28 | }); 29 | 30 | it('handles successful request', async () => { 31 | const responseHandler: ResponseHandler = (request) => { 32 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 33 | expect(request.queryParams).toEqual({}); 34 | expect(JSON.parse(request.requestBody)).toEqual({ 35 | username: 'example_username', 36 | password: '', 37 | 'grant_type': 'password', 38 | 'client_id': 'outseta_auth_widget' 39 | }); 40 | expect(request.requestHeaders['content-type']).toBe('application/json'); 41 | 42 | return [ 43 | 200, 44 | {'Content-Type': 'application/json'}, 45 | JSON.stringify(exampleResponse) 46 | ]; 47 | }; 48 | server = new Pretender(function () { 49 | this.post('https://test-company.outseta.com/api/v1/tokens', responseHandler); 50 | }); 51 | 52 | expect(store.userAuth.accessToken).toBeUndefined(); 53 | 54 | const response = await user.impersonate('example_username'); 55 | 56 | expect(store.userAuth.accessToken).toBe(exampleResponse.access_token); 57 | expect(response.access_token).toBe(exampleResponse.access_token); 58 | expect(response.expires_in).toBe(exampleResponse.expires_in); 59 | expect(response.token_type).toBe(exampleResponse.token_type); 60 | }); 61 | 62 | it('throws failed request', async () => { 63 | const responseHandler: ResponseHandler = (request) => { 64 | expect(request.requestHeaders['authorization']).toBe('Outseta example_key:example_secret'); 65 | expect(request.queryParams).toEqual({}); 66 | expect(JSON.parse(request.requestBody)).toEqual({ 67 | username: 'example_username', 68 | password: '', 69 | 'grant_type': 'password', 70 | 'client_id': 'outseta_auth_widget' 71 | }); 72 | expect(request.requestHeaders['content-type']).toBe('application/json'); 73 | 74 | return [ 75 | 500, 76 | {'Content-Type': 'application/json'}, 77 | JSON.stringify({}) 78 | ]; 79 | }; 80 | server = new Pretender(function () { 81 | this.post('https://test-company.outseta.com/api/v1/tokens', responseHandler); 82 | }); 83 | 84 | let exception; 85 | let response; 86 | 87 | try { 88 | response = await user.impersonate('example_username'); 89 | } catch (e) { 90 | exception = e; 91 | } 92 | 93 | expect(response).toBeUndefined(); 94 | expect(exception.status).toBe(500); 95 | }); 96 | }); 97 | }); 98 | }); 99 | 100 | const exampleResponse = { 101 | "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImpWcmlCLUhmLWF4N015TFZFQmVfUmcwR1JsayIsImtpZCI6ImpWcmlCLUhmLWF4N015TFZFQmVfUmcwR1JsayJ9.eyJuYmYiOjE2MTExNTg5MTgsImV4cCI6MTYxMTc2MzcxOCwiaXNzIjoiaHR0cDovL2Jvb3RzYWFzLm91dHNldGEuY29tIiwiY2xpZW50X2lkIjoiYm9vdHNhYXMub3V0c2V0YS5jb20ucmVzb3VyY2Utb3duZXIiLCJzY29wZSI6WyJvcGVuaWQiLCJvdXRzZXRhIiwicHJvZmlsZSJdLCJzdWIiOiJEUTJEeWtuVyIsImF1dGhfdGltZSI6MTYxMTE1ODkxOCwiaWRwIjoiaWRzcnYiLCJlbWFpbCI6ImRlbW9AYm9vdHNhYXMuY28iLCJnaXZlbl9uYW1lIjoiIiwiZmFtaWx5X25hbWUiOiIiLCJuYW1lIjoiICIsIm5hbWVpZCI6IkRRMkR5a25XIiwib3V0c2V0YTphY2NvdW50VWlkIjoiRTlMeTNQV3ciLCJvdXRzZXRhOmlzUHJpbWFyeSI6IjEiLCJhbXIiOlsicGFzc3dvcmQiXSwib3V0c2V0YTppc3MiOiIiLCJhdWQiOiJib290c2Fhcy5vdXRzZXRhLmNvbSJ9.bi5JOdFSh-nEfwdmaZSRUYb3_s2NYQWeDaBufvPRzm747YgnZgrQybfcg7d1AGfE0FDkVbO_4WXPd14BxILzGrGXjf9wYy1SSFFFdhIo4OtdTPAE76E9SeaW0HkvWNleXZ0XUp97vXLkU8IOeTHtRKvRdVq_i72v565LNFPdOKqU81mdYNo7FO1lz8wUb5grm2_AjLQW0ORLNbmZSxTURIjS8ZhgA07fTOjkvVcTAXI0b0hSIbasVpcJNNfTPinYT9xH-HlfsVCiVurPpTmc0fhllaQ3HvDcuqPmWjcGZbEWiPYyiFG07lwo0JMTpYvx7LB12jBlOZ8901qKYAEfOg", 102 | "expires_in": 604800, 103 | "token_type": "Bearer" 104 | }; 105 | -------------------------------------------------------------------------------- /test/api/user/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { User } from '../../../src/api/user'; 2 | import { Password } from '../../../src/api/user/password'; 3 | import { Profile } from '../../../src/api/user/profile'; 4 | import { Store } from '../../../src/util/store'; 5 | import { ServerCredentials, UserCredentials } from '../../../src/util/credentials'; 6 | 7 | describe('api', () => 8 | describe('User', () => 9 | describe('constructor', () => { 10 | it('creates successfully', () => { 11 | const user = new User( 12 | new Store( 13 | 'https://test-company.outseta.com/api/', 14 | new UserCredentials(), 15 | new ServerCredentials() 16 | ) 17 | ); 18 | 19 | expect(user).toBeInstanceOf(User); 20 | expect(user.password).toBeInstanceOf(Password); 21 | expect(user.profile).toBeInstanceOf(Profile); 22 | }); 23 | }) 24 | ) 25 | ); 26 | -------------------------------------------------------------------------------- /test/api/user/login.spec.ts: -------------------------------------------------------------------------------- 1 | import Pretender, { ResponseHandler } from 'pretender'; 2 | import { Store } from '../../../src/util/store'; 3 | import { User } from '../../../src/api/user'; 4 | import { ServerCredentials, UserCredentials } from '../../../src/util/credentials'; 5 | 6 | describe('api', () => { 7 | describe('User', () => { 8 | describe('login', () => { 9 | let server: Pretender; 10 | let store: Store; 11 | 12 | let user: User; 13 | 14 | beforeEach(() => { 15 | if (server) server.shutdown(); 16 | 17 | store = new Store( 18 | 'https://test-company.outseta.com/api/v1/', 19 | new UserCredentials(), 20 | new ServerCredentials() 21 | ); 22 | 23 | user = new User(store); 24 | }); 25 | 26 | afterAll(() => { 27 | server.shutdown(); 28 | }); 29 | 30 | it('handles successful request', async () => { 31 | const responseHandler: ResponseHandler = (request) => { 32 | expect(request.requestHeaders['authorization']).toBeUndefined(); 33 | expect(request.queryParams).toEqual({}); 34 | expect(JSON.parse(request.requestBody)).toEqual({ 35 | username: 'example_username', 36 | password: 'example_password', 37 | 'grant_type': 'password', 38 | 'client_id': 'outseta_auth_widget' 39 | }); 40 | expect(request.requestHeaders['content-type']).toBe('application/json'); 41 | 42 | return [ 43 | 200, 44 | {'Content-Type': 'application/json'}, 45 | JSON.stringify(exampleResponse) 46 | ]; 47 | }; 48 | server = new Pretender(function () { 49 | this.post('https://test-company.outseta.com/api/v1/tokens', responseHandler); 50 | }); 51 | 52 | expect(store.userAuth.accessToken).toBeUndefined(); 53 | 54 | const response = await user.login('example_username', 'example_password'); 55 | 56 | expect(store.userAuth.accessToken).toBe(exampleResponse.access_token); 57 | expect(response.access_token).toBe(exampleResponse.access_token); 58 | expect(response.expires_in).toBe(exampleResponse.expires_in); 59 | expect(response.token_type).toBe(exampleResponse.token_type); 60 | }); 61 | 62 | it('throws failed request', async () => { 63 | const responseHandler: ResponseHandler = (request) => { 64 | expect(request.requestHeaders['authorization']).toBeUndefined(); 65 | expect(request.queryParams).toEqual({}); 66 | expect(JSON.parse(request.requestBody)).toEqual({ 67 | username: 'example_username', 68 | password: 'example_password', 69 | 'grant_type': 'password', 70 | 'client_id': 'outseta_auth_widget' 71 | }); 72 | expect(request.requestHeaders['content-type']).toBe('application/json'); 73 | 74 | return [ 75 | 500, 76 | {'Content-Type': 'application/json'}, 77 | JSON.stringify({}) 78 | ]; 79 | }; 80 | server = new Pretender(function () { 81 | this.post('https://test-company.outseta.com/api/v1/tokens', responseHandler); 82 | }); 83 | 84 | let exception; 85 | let response; 86 | 87 | try { 88 | response = await user.login('example_username', 'example_password'); 89 | } catch (e) { 90 | exception = e; 91 | } 92 | 93 | expect(response).toBeUndefined(); 94 | expect(exception.status).toBe(500); 95 | }); 96 | }); 97 | }); 98 | }); 99 | 100 | const exampleResponse = { 101 | "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImpWcmlCLUhmLWF4N015TFZFQmVfUmcwR1JsayIsImtpZCI6ImpWcmlCLUhmLWF4N015TFZFQmVfUmcwR1JsayJ9.eyJuYmYiOjE2MTExNTg5MTgsImV4cCI6MTYxMTc2MzcxOCwiaXNzIjoiaHR0cDovL2Jvb3RzYWFzLm91dHNldGEuY29tIiwiY2xpZW50X2lkIjoiYm9vdHNhYXMub3V0c2V0YS5jb20ucmVzb3VyY2Utb3duZXIiLCJzY29wZSI6WyJvcGVuaWQiLCJvdXRzZXRhIiwicHJvZmlsZSJdLCJzdWIiOiJEUTJEeWtuVyIsImF1dGhfdGltZSI6MTYxMTE1ODkxOCwiaWRwIjoiaWRzcnYiLCJlbWFpbCI6ImRlbW9AYm9vdHNhYXMuY28iLCJnaXZlbl9uYW1lIjoiIiwiZmFtaWx5X25hbWUiOiIiLCJuYW1lIjoiICIsIm5hbWVpZCI6IkRRMkR5a25XIiwib3V0c2V0YTphY2NvdW50VWlkIjoiRTlMeTNQV3ciLCJvdXRzZXRhOmlzUHJpbWFyeSI6IjEiLCJhbXIiOlsicGFzc3dvcmQiXSwib3V0c2V0YTppc3MiOiIiLCJhdWQiOiJib290c2Fhcy5vdXRzZXRhLmNvbSJ9.bi5JOdFSh-nEfwdmaZSRUYb3_s2NYQWeDaBufvPRzm747YgnZgrQybfcg7d1AGfE0FDkVbO_4WXPd14BxILzGrGXjf9wYy1SSFFFdhIo4OtdTPAE76E9SeaW0HkvWNleXZ0XUp97vXLkU8IOeTHtRKvRdVq_i72v565LNFPdOKqU81mdYNo7FO1lz8wUb5grm2_AjLQW0ORLNbmZSxTURIjS8ZhgA07fTOjkvVcTAXI0b0hSIbasVpcJNNfTPinYT9xH-HlfsVCiVurPpTmc0fhllaQ3HvDcuqPmWjcGZbEWiPYyiFG07lwo0JMTpYvx7LB12jBlOZ8901qKYAEfOg", 102 | "expires_in": 604800, 103 | "token_type": "Bearer" 104 | }; 105 | -------------------------------------------------------------------------------- /test/api/user/password/update.spec.ts: -------------------------------------------------------------------------------- 1 | import Pretender, { ResponseHandler } from 'pretender'; 2 | import { Store } from '../../../../src/util/store'; 3 | import { Password } from '../../../../src/api/user/password'; 4 | import { ServerCredentials, UserCredentials } from '../../../../src/util/credentials'; 5 | 6 | describe('api', () => { 7 | describe('User', () => { 8 | describe('Password', () => { 9 | describe('update', () => { 10 | let server: Pretender; 11 | let store: Store; 12 | 13 | let password: Password; 14 | 15 | beforeEach(() => { 16 | if (server) server.shutdown(); 17 | 18 | store = new Store( 19 | 'https://test-company.outseta.com/api/v1/', 20 | new UserCredentials('example_token'), 21 | new ServerCredentials() 22 | ); 23 | 24 | password = new Password(store); 25 | }); 26 | 27 | afterAll(() => { 28 | server.shutdown(); 29 | }); 30 | 31 | it('handles successful request', async () => { 32 | const responseHandler: ResponseHandler = (request) => { 33 | expect(request.requestHeaders['authorization']).toBe('bearer example_token'); 34 | expect(request.queryParams).toEqual({}); 35 | expect(JSON.parse(request.requestBody)).toEqual({ 36 | ExistingPassword: 'testpass', 37 | NewPassword: 'testpass2' 38 | }); 39 | expect(request.requestHeaders['content-type']).toBe('application/json'); 40 | 41 | return [ 42 | 200, 43 | {'Content-Type': 'application/json'}, 44 | '' 45 | ]; 46 | }; 47 | server = new Pretender(function () { 48 | this.put('https://test-company.outseta.com/api/v1/profile/password', responseHandler); 49 | }); 50 | 51 | const response = await password.update('testpass', 'testpass2'); 52 | expect(response).toBeNull(); 53 | }); 54 | 55 | it('handles validation errors', async () => { 56 | const responseHandler: ResponseHandler = (request) => { 57 | expect(request.requestHeaders['authorization']).toBe('bearer example_token'); 58 | expect(request.queryParams).toEqual({}); 59 | expect(JSON.parse(request.requestBody)).toEqual({ 60 | ExistingPassword: 'testpasss', 61 | NewPassword: 'testpass2' 62 | }); 63 | expect(request.requestHeaders['content-type']).toBe('application/json'); 64 | 65 | return [ 66 | 400, 67 | {'Content-Type': 'application/json'}, 68 | JSON.stringify(exampleValidationResponse) 69 | ]; 70 | }; 71 | server = new Pretender(function () { 72 | this.put('https://test-company.outseta.com/api/v1/profile/password', responseHandler); 73 | }); 74 | 75 | const response = await password.update('testpasss', 'testpass2'); 76 | expect(response?.EntityValidationErrors[0].ValidationErrors).toHaveSize(1); 77 | expect(response?.EntityValidationErrors[0].ValidationErrors[0]).toEqual({ 78 | ErrorCode: "currentPasswordMismatch", 79 | ErrorMessage: "The current password does not match", 80 | PropertyName: "Password" 81 | }); 82 | }); 83 | 84 | it('throws failed request', async () => { 85 | const responseHandler: ResponseHandler = (request) => { 86 | expect(request.requestHeaders['authorization']).toBe('bearer example_token'); 87 | expect(request.queryParams).toEqual({}); 88 | expect(JSON.parse(request.requestBody)).toEqual({ 89 | ExistingPassword: 'testpass', 90 | NewPassword: 'testpass2' 91 | }); 92 | expect(request.requestHeaders['content-type']).toBe('application/json'); 93 | 94 | return [ 95 | 500, 96 | {'Content-Type': 'application/json'}, 97 | JSON.stringify({ "Message": "An error has occurred." }) 98 | ]; 99 | }; 100 | server = new Pretender(function () { 101 | this.put('https://test-company.outseta.com/api/v1/profile/password', responseHandler); 102 | }); 103 | 104 | let exception; 105 | let response; 106 | 107 | try { 108 | response = await password.update('testpass', 'testpass2'); 109 | } catch (e) { 110 | exception = e; 111 | } 112 | 113 | expect(response).toBeUndefined(); 114 | expect(exception.status).toBe(500); 115 | }); 116 | }); 117 | }); 118 | }); 119 | }); 120 | 121 | const exampleValidationResponse = { 122 | "ErrorMessage": "A validation error has occurred", 123 | "EntityValidationErrors": [ 124 | { 125 | "Entity": { 126 | "Email": "demo@tiltcamp.com", 127 | "FirstName": "Jane", 128 | "LastName": "Doe", 129 | "MailingAddress": { 130 | "AddressLine1": '152 Test Lane', 131 | "AddressLine2": 'Apt L', 132 | "AddressLine3": null, 133 | "City": "San Diego", 134 | "State": "CA", 135 | "PostalCode": "91511", 136 | "Country": "United States of America", 137 | "Uid": "yW1Kj1QB", 138 | "Created": "2021-01-20T05:24:53", 139 | "Updated": "2021-01-24T01:20:53" 140 | }, 141 | "PasswordMustChange": false, 142 | "PhoneMobile": "4084841547", 143 | "PhoneWork": "4122144785", 144 | "Title": "Engineer", 145 | "Timezone": null, 146 | "Language": null, 147 | "IPAddress": null, 148 | "Referer": null, 149 | "UserAgent": null, 150 | "LastLoginDateTime": null, 151 | "OAuthGoogleProfileId": null, 152 | "PersonAccount": [ 153 | { 154 | "Person": null, 155 | "Account": null, 156 | "IsPrimary": true, 157 | "Uid": "496L7AmX", 158 | "Created": "2021-01-20T05:25:56", 159 | "Updated": "2021-01-20T05:25:56" 160 | } 161 | ], 162 | "DealPeople": [], 163 | "Account": null, 164 | "FullName": "Jane Doe", 165 | "OAuthIntegrationStatus": 0, 166 | "UserAgentPlatformBrowser": "", 167 | "Uid": "DQ2DyknW", 168 | "Created": "2021-01-20T05:24:53", 169 | "Updated": "2021-01-24T01:21:03" 170 | }, 171 | "TypeName": "Person", 172 | "ValidationErrors": [ 173 | { 174 | "ErrorCode": "currentPasswordMismatch", 175 | "ErrorMessage": "The current password does not match", 176 | "PropertyName": "Password" 177 | } 178 | ] 179 | } 180 | ] 181 | }; 182 | -------------------------------------------------------------------------------- /test/api/user/profile/get.spec.ts: -------------------------------------------------------------------------------- 1 | import Pretender, { ResponseHandler } from 'pretender'; 2 | import { Store } from '../../../../src/util/store'; 3 | import { Profile } from '../../../../src/api/user/profile'; 4 | import { ServerCredentials, UserCredentials } from '../../../../src/util/credentials'; 5 | 6 | describe('api', () => { 7 | describe('User', () => { 8 | describe('Profile', () => { 9 | describe('get', () => { 10 | let server: Pretender; 11 | let store: Store; 12 | 13 | let profile: Profile; 14 | 15 | beforeEach(() => { 16 | if (server) server.shutdown(); 17 | 18 | store = new Store( 19 | 'https://test-company.outseta.com/api/v1/', 20 | new UserCredentials('example_token'), 21 | new ServerCredentials() 22 | ); 23 | 24 | profile = new Profile(store); 25 | }); 26 | 27 | afterAll(() => { 28 | server.shutdown(); 29 | }); 30 | 31 | it('handles successful request', async () => { 32 | const responseHandler: ResponseHandler = (request) => { 33 | expect(request.requestHeaders['authorization']).toBe('bearer example_token'); 34 | expect(request.queryParams).toEqual({}); 35 | expect(request.requestBody).toBeNull(); 36 | expect(request.requestHeaders['content-type']).toBe('application/json'); 37 | 38 | return [ 39 | 200, 40 | {'Content-Type': 'application/json'}, 41 | JSON.stringify(exampleResponse) 42 | ]; 43 | }; 44 | server = new Pretender(function () { 45 | this.get('https://test-company.outseta.com/api/v1/profile', responseHandler); 46 | }); 47 | 48 | const response = await profile.get(); 49 | expect(response.Email).toBe('demo@tiltcamp.com'); 50 | }); 51 | 52 | it('handles request with fields', async () => { 53 | const responseHandler: ResponseHandler = (request) => { 54 | expect(request.requestHeaders['authorization']).toBe('bearer example_token'); 55 | expect(request.queryParams).toEqual({ fields: 'Uid' }); 56 | expect(request.requestBody).toBeNull(); 57 | expect(request.requestHeaders['content-type']).toBe('application/json'); 58 | 59 | return [ 60 | 200, 61 | {'Content-Type': 'application/json'}, 62 | JSON.stringify(exampleResponse) 63 | ]; 64 | }; 65 | server = new Pretender(function () { 66 | this.get('https://test-company.outseta.com/api/v1/profile', responseHandler); 67 | }); 68 | 69 | const response = await profile.get({ fields: 'Uid' }); 70 | expect(response.Uid).toBe('DQ2DyknW'); 71 | }); 72 | 73 | it('throws failed request', async () => { 74 | const responseHandler: ResponseHandler = (request) => { 75 | expect(request.requestHeaders['authorization']).toBe('bearer example_token'); 76 | expect(request.queryParams).toEqual({}); 77 | expect(request.requestBody).toBeNull(); 78 | expect(request.requestHeaders['content-type']).toBe('application/json'); 79 | 80 | return [ 81 | 500, 82 | {'Content-Type': 'application/json'}, 83 | JSON.stringify({ "Message": "An error has occurred." }) 84 | ]; 85 | }; 86 | server = new Pretender(function () { 87 | this.get('https://test-company.outseta.com/api/v1/profile', responseHandler); 88 | }); 89 | 90 | let exception; 91 | let response; 92 | 93 | try { 94 | response = await profile.get(); 95 | } catch (e) { 96 | exception = e; 97 | } 98 | 99 | expect(response).toBeUndefined(); 100 | expect(exception.status).toBe(500); 101 | }); 102 | }); 103 | }); 104 | }); 105 | }); 106 | 107 | const exampleResponse = { 108 | "Email": "demo@tiltcamp.com", 109 | "FirstName": "", 110 | "LastName": "", 111 | "MailingAddress": { 112 | "AddressLine1": "152 Test Lane", 113 | "AddressLine2": "Apt L", 114 | "AddressLine3": null, 115 | "City": "San Diego", 116 | "State": "CA", 117 | "PostalCode": "91511", 118 | "Country": "United States of America", 119 | "Uid": "yW1Kj1QB", 120 | "Created": "2021-01-20T05:24:53", 121 | "Updated": "2021-01-20T05:24:53" 122 | }, 123 | "PasswordMustChange": false, 124 | "PhoneMobile": "4084841547", 125 | "PhoneWork": "4122144785", 126 | "Title": "Engineer", 127 | "Timezone": null, 128 | "Language": null, 129 | "IPAddress": null, 130 | "Referer": null, 131 | "UserAgent": null, 132 | "LastLoginDateTime": null, 133 | "OAuthGoogleProfileId": null, 134 | "PersonAccount": [ 135 | { 136 | "Person": null, 137 | "Account": null, 138 | "IsPrimary": true, 139 | "Uid": "496L7AmX", 140 | "Created": "2021-01-20T05:25:56", 141 | "Updated": "2021-01-20T05:25:56" 142 | } 143 | ], 144 | "DealPeople": [], 145 | "Account": null, 146 | "FullName": "demo@tiltcamp.com", 147 | "OAuthIntegrationStatus": 0, 148 | "UserAgentPlatformBrowser": "", 149 | "Uid": "DQ2DyknW", 150 | "Created": "2021-01-20T05:24:53", 151 | "Updated": "2021-01-20T05:37:38" 152 | }; 153 | -------------------------------------------------------------------------------- /test/index.spec.ts: -------------------------------------------------------------------------------- 1 | import Outseta from '../src'; 2 | import { Crm } from '../src/api/crm'; 3 | import { Marketing } from '../src/api/marketing'; 4 | import { Support } from '../src/api/support'; 5 | import { User } from '../src/api/user'; 6 | import { Billing } from '../src/api/billing'; 7 | 8 | describe('constructor', () => { 9 | it('creates successfully', () => { 10 | const outseta = new Outseta({ 11 | subdomain: 'example' 12 | }); 13 | 14 | expect(outseta).toBeInstanceOf(Outseta); 15 | expect(outseta.billing).toBeInstanceOf(Billing); 16 | expect(outseta.crm).toBeInstanceOf(Crm); 17 | expect(outseta.marketing).toBeInstanceOf(Marketing); 18 | expect(outseta.support).toBeInstanceOf(Support); 19 | expect(outseta.user).toBeInstanceOf(User); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /test/util/credentials.spec.ts: -------------------------------------------------------------------------------- 1 | import { UserCredentials, ServerCredentials } from '../../src/util/credentials'; 2 | 3 | describe('util', () => 4 | describe('Credentials', () => { 5 | describe('ServerCredentials', () => { 6 | describe('authorizationHeader', () => { 7 | it('returns header for server-side API keys', () => { 8 | const credentials = new ServerCredentials('example_api_key', 'example_secret_key'); 9 | expect(credentials.authorizationHeader).toBe('Outseta example_api_key:example_secret_key'); 10 | }); 11 | 12 | it('throws an exception if there aren\'t any API keys', () => { 13 | let exception; 14 | const credentials = new ServerCredentials(); 15 | 16 | try { 17 | credentials.authorizationHeader; 18 | } catch (e) { 19 | exception = e; 20 | } 21 | 22 | expect(exception).toBe( 23 | 'The API client was not initialized with API keys.' 24 | ); 25 | }); 26 | }); 27 | 28 | describe('isReady', () => { 29 | it('returns true if API key is available', () => { 30 | const credentials = new ServerCredentials('example_key', 'example_secret'); 31 | expect(credentials.isReady).toBeTrue(); 32 | }); 33 | 34 | it('returns false if API key is not provided', () => { 35 | let credentials = new ServerCredentials(); 36 | expect(credentials.isReady).toBeFalse(); 37 | 38 | credentials = new ServerCredentials('example_api_key'); 39 | expect(credentials.isReady).toBeFalse(); 40 | 41 | credentials = new ServerCredentials('', 'example_secret_key'); 42 | expect(credentials.isReady).toBeFalse(); 43 | }); 44 | }); 45 | }); 46 | 47 | describe('UserCredentials', () => { 48 | describe('authorizationHeader', () => { 49 | it('returns header for client-side access token', () => { 50 | const credentials = new UserCredentials('example_token'); 51 | expect(credentials.authorizationHeader).toBe('bearer example_token'); 52 | }); 53 | 54 | it('throws an exception if there aren\'t any API keys', () => { 55 | let exception; 56 | const credentials = new UserCredentials(); 57 | 58 | try { 59 | credentials.authorizationHeader; 60 | } catch (e) { 61 | exception = e; 62 | } 63 | 64 | expect(exception).toBe( 65 | 'The API client doesn\'t have a user token. Please initialize the client with one or ' + 66 | 'call profile.login() first.' 67 | ); 68 | }); 69 | }); 70 | 71 | describe('isReady', () => { 72 | it('returns true if access token is available', () => { 73 | let credentials = new UserCredentials('example_token'); 74 | expect(credentials.isReady).toBeTrue(); 75 | 76 | credentials = new UserCredentials(); 77 | credentials.accessToken = 'example_token'; 78 | expect(credentials.isReady).toBeTrue(); 79 | }); 80 | 81 | it('returns false if access token is not provided', () => { 82 | let credentials = new UserCredentials(); 83 | expect(credentials.isReady).toBeFalse(); 84 | 85 | credentials = new UserCredentials(''); 86 | expect(credentials.isReady).toBeFalse(); 87 | }); 88 | }); 89 | }); 90 | }) 91 | ); 92 | -------------------------------------------------------------------------------- /tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "target": "es5", 5 | "module": "commonjs", 6 | "outDir": "./dist" 7 | }, 8 | "files": [ 9 | "src/index.ts" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "target": "ES2015", 5 | "module": "ES2020", 6 | "outDir": "./dist" 7 | }, 8 | "files": [ 9 | "src/index.ts" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "target": "ES2015", 5 | "module": "ES2020", 6 | "lib": ["es2017", "es7", "es6", "dom"], 7 | "outDir": "./dist", 8 | "declaration": true, 9 | "strict": true, 10 | "esModuleInterop": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "moduleResolution": "node" 13 | }, 14 | "exclude": [ 15 | "node_modules", 16 | "dist" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "emitDecoratorMetadata": true, 5 | "experimentalDecorators": true, 6 | "types": [ 7 | "jasmine" 8 | ], 9 | "module": "CommonJS", 10 | "sourceMap": true, 11 | "target": "ES5" 12 | } 13 | } -------------------------------------------------------------------------------- /typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "entryPoints": ["src/"], 3 | "out": "docs" 4 | } --------------------------------------------------------------------------------