diff --git a/.c8rc.json b/.c8rc.json new file mode 100644 index 0000000..daa7349 --- /dev/null +++ b/.c8rc.json @@ -0,0 +1,20 @@ +{ + "all": true, + "include": [ + "lib/**/*.js" + ], + "exclude": [ + "test/**", + "**/node_modules/**" + ], + "reporter": [ + "text", + "text-summary", + "lcov" + ], + "check-coverage": true, + "lines": 70, + "statements": 70, + "functions": 70, + "branches": 60 +} diff --git a/.eslintrc.json b/.eslintrc.json index 5807666..b1093cf 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,6 +1,22 @@ { "extends": "airbnb-base", + "parserOptions": { + "ecmaVersion": 2022, + "sourceType": "module" + }, + "env": { + "node": true, + "es2022": true + }, "rules": { - "import/no-extraneous-dependencies": ["error", {"devDependencies": true}] - } -} \ No newline at end of file + "import/no-extraneous-dependencies": ["error", {"devDependencies": true}], + "import/extensions": ["error", "ignorePackages", {"js": "always"}], + "prefer-destructuring": ["error", {"array": false}] + }, + "overrides": [ + { + "files": ["test/**/*.js"], + "env": {"mocha": true} + } + ] +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..bf6abb2 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,82 @@ +# Continuous Integration: runs on every pull request and on pushes to master. +# Complements npm-publish.yml (which only runs at release time) by giving +# fast feedback on lint, tests, and security advisories before code is merged. + +name: CI + +# Least-privilege default for GITHUB_TOKEN: all jobs here only read the repo +# (checkout + npm). Individual jobs can escalate with a job-level permissions +# key if they ever need more. +permissions: + contents: read + +on: + pull_request: + branches: [master] + push: + branches: [master] + +jobs: + test: + name: Test (Node ${{ matrix.node-version }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + # Cover the declared floor (20) and current/next LTS lines so a + # runtime regression (e.g. the removed `assert {}` import syntax) is + # caught here instead of at release. + node-version: [20, 22, 24] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: npm + - run: npm ci + - run: npm test + + coverage: + name: Coverage + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + - run: npm ci + # Runs the suite under c8 and enforces the thresholds in .c8rc.json. + - run: npm run coverage + - uses: actions/upload-artifact@v4 + if: always() + with: + name: coverage-lcov + path: coverage/lcov.info + if-no-files-found: ignore + + audit: + name: Security audit + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + - run: npm ci + # Fail only on high/critical advisories. Lower-severity findings are + # reported by Dependabot; tighten this threshold once the tree is clean. + - run: npm audit --audit-level=high + + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + - run: npm ci + - run: npm run lint diff --git a/index.js b/index.js index 2e5a731..1e5e002 100644 --- a/index.js +++ b/index.js @@ -1,2 +1,3 @@ import onfleet from './lib/onfleet.js'; -export default onfleet; \ No newline at end of file + +export default onfleet; diff --git a/lib/Methods.js b/lib/Methods.js index b0f1b0f..8fab19c 100644 --- a/lib/Methods.js +++ b/lib/Methods.js @@ -66,7 +66,7 @@ const Methods = async (key, api, ...args) => { if (args.length === 0 && operations === 'GET' && altPath) { url = `${api.api.baseUrl}${altPath}`; } - + // 1 or more arguments if (args.length >= 1 && ['GET', 'DELETE', 'PUT'].includes(operations)) { if (['name', 'shortId', 'phone', 'workers', 'organizations', 'teams'].includes(args[1])) { @@ -102,11 +102,11 @@ const Methods = async (key, api, ...args) => { // Query Params extension if (queryParams) { - for (const element of args) { + args.forEach((element) => { if (util.isQueryParam(element)) { url = util.appendQueryParameters(url, element); } - } + }); } // Reference https://docs.onfleet.com/reference/delivery-manifest @@ -115,68 +115,64 @@ const Methods = async (key, api, ...args) => { if (item.hubId && item.workerId) { body = { path: `providers/manifest/generate?hubId=${item.hubId}&workerId=${item.workerId}`, - method: "GET", + method: 'GET', }; hasBody = true; } if (item.googleApiKey) { - api.api.headers["X-API-Key"] = `Google ${item.googleApiKey}`; + // eslint-disable-next-line no-param-reassign + api.api.headers['X-API-Key'] = `Google ${item.googleApiKey}`; } if (item.startDate || item.endDate) { - const queryParams = {}; - if (item.startDate) queryParams.startDate = item.startDate; - if (item.endDate) queryParams.endDate = item.endDate; - url = util.appendQueryParameters(url, queryParams); + const dateQueryParams = {}; + if (item.startDate) dateQueryParams.startDate = item.startDate; + if (item.endDate) dateQueryParams.endDate = item.endDate; + url = util.appendQueryParameters(url, dateQueryParams); } }); } // Send the HTTP request through the rate limiter - try { - const res = await limiter.schedule(() => fetch(url, { - method: operations, - headers: api.api.headers, - timeout: timeoutInMilliseconds, - body: hasBody ? JSON.stringify(body) : undefined, - })); - - // For every request, we compare the reservoir with the remaining rate limit in the header - const reservoir = await limiter.currentReservoir(); - const rateLimitRemaining = res.headers.get('x-ratelimit-remaining'); - if (reservoir < rateLimitRemaining) { - reassignRate(rateLimitRemaining); - } - - if (res.ok) { - if (operations === 'DELETE') { - return res.status; - } - return res.json().catch(() => res.status); - } + const res = await limiter.schedule(() => fetch(url, { + method: operations, + headers: api.api.headers, + timeout: timeoutInMilliseconds, + body: hasBody ? JSON.stringify(body) : undefined, + })); + + // For every request, we compare the reservoir with the remaining rate limit in the header + const reservoir = await limiter.currentReservoir(); + const rateLimitRemaining = res.headers.get('x-ratelimit-remaining'); + if (reservoir < rateLimitRemaining) { + reassignRate(rateLimitRemaining); + } - const error = await res.json(); - const errorCode = error.message.error; - const errorInfo = [ - error.message.message, - errorCode, - error.message.cause, - error.message.request, - ]; - - if (errorCode === 2300) { - throw new RateLimitError(...errorInfo); - } else if (errorCode >= 1100 && errorCode <= 1108) { - throw new PermissionError(...errorInfo); - } else if (errorCode >= 2500) { - throw new ServiceError(...errorInfo); - } else if (errorCode === 2218) { // Precondition error for Auto-Dispatch - throw new ServiceError(...errorInfo); + if (res.ok) { + if (operations === 'DELETE') { + return res.status; } - throw new HttpError(...errorInfo); + return res.json().catch(() => res.status); + } - } catch (error) { - throw error; + const error = await res.json(); + const errorCode = error.message.error; + const errorInfo = [ + error.message.message, + errorCode, + error.message.cause, + error.message.request, + ]; + + if (errorCode === 2300) { + throw new RateLimitError(...errorInfo); + } else if (errorCode >= 1100 && errorCode <= 1108) { + throw new PermissionError(...errorInfo); + } else if (errorCode >= 2500) { + throw new ServiceError(...errorInfo); + } else if (errorCode === 2218) { // Precondition error for Auto-Dispatch + throw new ServiceError(...errorInfo); } + throw new HttpError(...errorInfo); }; export default Methods; diff --git a/lib/error.js b/lib/error.js index c689d84..cd5f8e4 100644 --- a/lib/error.js +++ b/lib/error.js @@ -11,17 +11,13 @@ const createError = (name, message, status = null, cause = null, request = null) // Create specific error types using the factory function const ValidationError = (message) => createError('ValidationError', message); -const PermissionError = (message, status, cause, request) => - createError('PermissionError', message, status, cause, request); +const PermissionError = (message, status, cause, request) => createError('PermissionError', message, status, cause, request); -const HttpError = (message, status, cause, request) => - createError('HttpError', message, status, cause, request); +const HttpError = (message, status, cause, request) => createError('HttpError', message, status, cause, request); -const RateLimitError = (message, status, cause, request) => - createError('RateLimitError', message, status, cause, request); +const RateLimitError = (message, status, cause, request) => createError('RateLimitError', message, status, cause, request); -const ServiceError = (message, status, cause, request) => - createError('ServiceError', message, status, cause, request); +const ServiceError = (message, status, cause, request) => createError('ServiceError', message, status, cause, request); // Export the custom error creators export { diff --git a/lib/onfleet.js b/lib/onfleet.js index 560b7d3..2eebce1 100644 --- a/lib/onfleet.js +++ b/lib/onfleet.js @@ -1,14 +1,7 @@ -// Define constants for default values -const DEFAULT_URL = 'https://onfleet.com'; -const DEFAULT_PATH = '/api'; -const DEFAULT_API_VERSION = '/v2'; -const DEFAULT_TIMEOUT = 70000; - +import { createRequire } from 'module'; import * as constants from './constants.js'; import * as util from './util.js'; import { ValidationError } from './error.js'; -import packageData from '../package.json' assert { type: 'json' }; - import Admins from './resources/Administrators.js'; import Containers from './resources/Containers.js'; import Destinations from './resources/Destinations.js'; @@ -22,6 +15,17 @@ import Workers from './resources/Workers.js'; import Webhooks from './resources/Webhooks.js'; import CustomFields from './resources/CustomFields.js'; +// Define constants for default values +const DEFAULT_URL = 'https://onfleet.com'; +const DEFAULT_PATH = '/api'; +const DEFAULT_API_VERSION = '/v2'; +const DEFAULT_TIMEOUT = 70000; + +// Read package metadata without a JSON import attribute so the module loads +// consistently across Node versions and tooling (ESLint, bundlers). +const require = createRequire(import.meta.url); +const packageData = require('../package.json'); + const { name, version } = packageData; const resources = { @@ -84,6 +88,7 @@ class Onfleet { this.api.headers = { ...this.api.headers, ...headers }; } + // eslint-disable-next-line class-methods-use-this initBottleneckOptions({ LIMITER_RESERVOIR = constants.LIMITER_RESERVOIR, LIMITER_WAIT_UPON_DEPLETION = constants.LIMITER_WAIT_UPON_DEPLETION, @@ -99,14 +104,14 @@ class Onfleet { } initResources() { - Object.entries(resources).forEach(([name, Resource]) => { - const endpoint = name.toLowerCase(); + Object.entries(resources).forEach(([resourceName, Resource]) => { + const endpoint = resourceName.toLowerCase(); this[endpoint] = new Resource(this); }); } async verifyKey() { - return await util.authenticate(this.api); + return util.authenticate(this.api); } } diff --git a/lib/resources/Containers.js b/lib/resources/Containers.js index 331e51f..dcf69f2 100644 --- a/lib/resources/Containers.js +++ b/lib/resources/Containers.js @@ -15,4 +15,4 @@ export default class Containers extends Resource { }, }); } -}; +} diff --git a/lib/resources/Routeplan.js b/lib/resources/Routeplan.js index 1b6c571..3d30f9d 100644 --- a/lib/resources/Routeplan.js +++ b/lib/resources/Routeplan.js @@ -23,14 +23,14 @@ export default class Routeplan extends Resource { path: '/routePlans/:routePlanId', method: 'PUT', }, - addTasksToRoutePlan:{ - path:'/routePlans/:routePlanId/tasks', - method:'PUT', + addTasksToRoutePlan: { + path: '/routePlans/:routePlanId/tasks', + method: 'PUT', }, deleteOne: { path: '/routePlans/:routePlanId', method: 'DELETE', - } + }, }); } } diff --git a/lib/resources/Teams.js b/lib/resources/Teams.js index 2ea946d..aeb50a5 100644 --- a/lib/resources/Teams.js +++ b/lib/resources/Teams.js @@ -47,7 +47,7 @@ export default class Teams extends Resource { matchMetadata: { path: '/teams/metadata', method: 'POST', - } + }, }); } } diff --git a/lib/util.js b/lib/util.js index 7cceeed..99e72a6 100644 --- a/lib/util.js +++ b/lib/util.js @@ -52,6 +52,4 @@ export const appendQueryParameters = (url, queryObj) => { return path; }; -export const isQueryParam = (obj) => { - return typeof obj === 'object'; -}; +export const isQueryParam = (obj) => typeof obj === 'object'; diff --git a/package-lock.json b/package-lock.json index b299401..83a20ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "node-fetch": "^3.3.2" }, "devDependencies": { + "c8": "^11.0.0", "chai": "^5.1.1", "chai-as-promised": "^8.0.0", "eslint": "^8.57.0", @@ -26,6 +27,16 @@ "node": ">=20.0.0" } }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", @@ -127,6 +138,44 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -172,6 +221,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -468,13 +524,26 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/brace-expansion/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/braces": { @@ -497,6 +566,84 @@ "dev": true, "license": "ISC" }, + "node_modules/c8": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/c8/-/c8-11.0.0.tgz", + "integrity": "sha512-e/uRViGHSVIJv7zsaDKM7VRn2390TgHXqUSvYwPHBQaU6L7E9L0n9JbdkwdYPvshDT0KymBmmlwSpms3yBaMNg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.1", + "@istanbuljs/schema": "^0.1.3", + "find-up": "^5.0.0", + "foreground-child": "^3.1.1", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.1.6", + "test-exclude": "^8.0.0", + "v8-to-istanbul": "^9.0.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1" + }, + "bin": { + "c8": "bin/c8.js" + }, + "engines": { + "node": "20 || >=22" + }, + "peerDependencies": { + "monocart-coverage-reports": "^2" + }, + "peerDependenciesMeta": { + "monocart-coverage-reports": { + "optional": true + } + } + }, + "node_modules/c8/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/c8/node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/c8/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/call-bind": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", @@ -697,6 +844,13 @@ "dev": true, "license": "MIT" }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, "node_modules/confusing-browser-globals": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", @@ -704,6 +858,13 @@ "dev": true, "license": "MIT" }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1506,6 +1667,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/formdata-polyfill": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", @@ -1692,6 +1870,16 @@ "node": ">=10.13.0" } }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/glob/node_modules/minimatch": { "version": "5.1.9", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", @@ -1862,6 +2050,13 @@ "he": "bin/he" } }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -2395,6 +2590,45 @@ "dev": true, "license": "ISC" }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/js-yaml": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", @@ -2520,6 +2754,45 @@ "dev": true, "license": "MIT" }, + "node_modules/lru-cache": { + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", + "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -2543,6 +2816,17 @@ "node": "*" } }, + "node_modules/minimatch/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", @@ -2553,6 +2837,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/mocha": { "version": "10.8.2", "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", @@ -2589,6 +2883,16 @@ "node": ">= 14.0.0" } }, + "node_modules/mocha/node_modules/brace-expansion": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/mocha/node_modules/minimatch": { "version": "5.1.9", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", @@ -2955,6 +3259,23 @@ "dev": true, "license": "MIT" }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/pathval": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", @@ -3316,10 +3637,11 @@ "integrity": "sha512-F4LcB0UqUl1zErq+1nYEEzSHJnIwb3AF2XWB94b+afhrekOUijwooAYqFyRbjYkm2PAKBabx6oYv/xDxNi8IBw==", "dev": true, "license": "BSD-3-Clause", - "engines": { - "node": ">=20.0.0" "dependencies": { "randombytes": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" } }, "node_modules/set-function-length": { @@ -3470,6 +3792,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", @@ -3620,6 +3955,55 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/test-exclude": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-8.0.0.tgz", + "integrity": "sha512-ZOffsNrXYggvU1mDGHk54I96r26P8SyMjO5slMKSc7+IWmtB/MQKnEC2fP51imB3/pT6YK5cT5E8f+Dd9KdyOQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^13.0.6", + "minimatch": "^10.2.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -3786,6 +4170,21 @@ "punycode": "^2.1.0" } }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, "node_modules/web-streams-polyfill": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", diff --git a/package.json b/package.json index 48e944b..b591636 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "scripts": { "lint": "eslint --ext .js ./", "lint-fix": "eslint --fix --ext .js ./", - "test": "mocha" + "test": "mocha", + "coverage": "c8 mocha" }, "repository": { "type": "git", @@ -31,6 +32,7 @@ "node-fetch": "^3.3.2" }, "devDependencies": { + "c8": "^11.0.0", "chai": "^5.1.1", "chai-as-promised": "^8.0.0", "eslint": "^8.57.0", @@ -44,7 +46,10 @@ "node": ">=20.0.0" }, "overrides": { - "brace-expansion": "^2.0.2", + "brace-expansion@1": "^1.1.13", + "brace-expansion@2": "^2.0.3", + "brace-expansion@3": "^3.0.1", + "brace-expansion@4": "^4.0.1", "cross-spawn": "^7.0.6", "serialize-javascript": "7.0.5", "minimatch@3": "3.1.4" diff --git a/test/response.js b/test/response.js index dfa16b3..df1ab37 100644 --- a/test/response.js +++ b/test/response.js @@ -191,68 +191,156 @@ const testData = { manifestDate: 1694199600000, departureTime: 1694199600000, driver: { - name: "Test One", - phone: "+16265555768", + name: 'Test One', + phone: '+16265555768', }, vehicle: { - type: "CAR", - description: "Honda", - licensePlate: "12345687", - color: "Purple", + type: 'CAR', + description: 'Honda', + licensePlate: '12345687', + color: 'Purple', timeLastModified: 1692746334342, }, - hubAddress: "1111 South Figueroa Street, Los Angeles, California 90015", + hubAddress: '1111 South Figueroa Street, Los Angeles, California 90015', turnByTurn: [ { - start_address: "1403 W Pico Blvd, Los Angeles, CA 90015, USA", - end_address: "2695 E Katella Ave, Anaheim, CA 92806, USA", + start_address: '1403 W Pico Blvd, Los Angeles, CA 90015, USA', + end_address: '2695 E Katella Ave, Anaheim, CA 92806, USA', eta: 1692992466000, - driving_distance: "30.6 mi", + driving_distance: '30.6 mi', steps: [ - "Head southeast on 12th St E toward S Figueroa StPartial restricted usage road", - "Turn right onto Flower St", - "Turn left onto the Interstate 10 E ramp to 18th St", - "Merge onto I-10 E", - "Take the exit onto I-5 S toward Santa Ana", - "Take exit 109A for Katella Ave", - "Turn right onto E Katella AvePass by Comerica Bank (on the right in 1.3 mi)", - "Turn left onto S Douglass Rd", - "Turn right onto Stanley Cup Wy", - "Turn right" - ] - } + 'Head southeast on 12th St E toward S Figueroa StPartial restricted usage road', + 'Turn right onto Flower St', + 'Turn left onto the Interstate 10 E ramp to 18th St', + 'Merge onto I-10 E', + 'Take the exit onto I-5 S toward Santa Ana', + 'Take exit 109A for Katella Ave', + 'Turn right onto E Katella AvePass by Comerica Bank (on the right in 1.3 mi)', + 'Turn left onto S Douglass Rd', + 'Turn right onto Stanley Cup Wy', + 'Turn right', + ], + }, ], - totalDistance: null + totalDistance: null, }, createCustomFields: 200, getCustomFields: { fields: [ { - description: "this is a test", + description: 'this is a test', asArray: false, visibility: [ - "admin", - "api", - "worker" + 'admin', + 'api', + 'worker', ], editability: [ - "admin", - "api" + 'admin', + 'api', ], - key: "test", - name: "test", - type: "single_line_text_field", + key: 'test', + name: 'test', + type: 'single_line_text_field', contexts: [ { isRequired: false, conditions: [], - name: "save" - } + name: 'save', + }, ], - value: "order 123" - } - ] - } + value: 'order 123', + }, + ], + }, + getHubs: [ + { + id: 'tKxSfU7psqDQEBVn5e2VQ~*O', + name: 'Downtown Hub', + address: { + street: 'Market St', + number: '929', + city: 'San Francisco', + state: 'California', + country: 'United States', + postalCode: '94103', + }, + }, + ], + createHub: { + id: 'tKxSfU7psqDQEBVn5e2VQ~*O', + name: 'Downtown Hub', + }, + updateHub: { + id: 'tKxSfU7psqDQEBVn5e2VQ~*O', + name: 'Uptown Hub', + }, + createDestination: { + id: '9qcJpfoqLwDppaZO8wYPFfsT', + address: { + number: '543', + street: 'Howard Street', + city: 'San Francisco', + country: 'United States', + }, + location: [-122.3965731, 37.7875728], + }, + getDestination: { + id: '9qcJpfoqLwDppaZO8wYPFfsT', + address: { + city: 'San Francisco', + }, + location: [-122.3965731, 37.7875728], + }, + matchDestinations: [ + { + id: '9qcJpfoqLwDppaZO8wYPFfsT', + }, + ], + getContainer: { + type: 'TEAM', + team: 'K3FXFtJj2FtaO2~H60evRrDc', + organization: 'BxvqsKQBEeKGMeAsN09ScrVt', + tasks: [], + }, + getOrganization: { + id: 'cArbDoYDcQ8tdQUghBp3xrLj', + name: 'Onfleet Organization', + email: 'support@onfleet.com', + timezone: 'America/Los_Angeles', + country: 'US', + }, + createWebhook: { + id: '2bcSv6Qd~OpvThCqkcdrq6w0', + count: 0, + url: 'https://www.example.com/onfleet', + trigger: 0, + isEnabled: true, + }, + getWebhooks: [ + { + id: '2bcSv6Qd~OpvThCqkcdrq6w0', + url: 'https://www.example.com/onfleet', + trigger: 0, + isEnabled: true, + }, + ], + createRoutePlan: { + id: 'aBcDeFgHiJkLmNoPqRsTuVwX', + name: 'My Route Plan', + state: 'PENDING', + color: '#ff0000', + tasks: [], + }, + getRoutePlan: { + id: 'aBcDeFgHiJkLmNoPqRsTuVwX', + name: 'My Route Plan', + state: 'PLANNED', + }, + updateRoutePlan: { + id: 'aBcDeFgHiJkLmNoPqRsTuVwX', + name: 'Updated Route Plan', + }, }; // Export testData diff --git a/test/test.js b/test/test.js index 58d35fd..7331596 100644 --- a/test/test.js +++ b/test/test.js @@ -40,37 +40,37 @@ const deliveryManifestObject = { workerId: 'kBUZAb7pREtRn*8wIUCpjnPu', googleApiKey: '', startDate: '1455072025000', - endDate: '1455072025000' + endDate: '1455072025000', }; const createCustomField = { model: 'Task', field: [{ - "description": "this is a test", - "asArray": false, - "visibility": [ - "admin", - "api", - "worker" + description: 'this is a test', + asArray: false, + visibility: [ + 'admin', + 'api', + 'worker', ], - "editability": [ - "admin", - "api" + editability: [ + 'admin', + 'api', ], - "key": "test", - "name": "test", - "type": "single_line_text_field", - "contexts": [ + key: 'test', + name: 'test', + type: 'single_line_text_field', + contexts: [ { - "isRequired": false, - "conditions": [], - "name": "save" - } + isRequired: false, + conditions: [], + name: 'save', + }, ], - "value": "order 123" + value: 'order 123', }], - integration: "shopify" -} + integration: 'shopify', +}; chai.use(chaiAsPromised); @@ -85,7 +85,10 @@ describe('Utility functions testing', () => { assert.equal(util.replaceWithEndpointAndParam(response.url, 'phone', response.phone), response.pathWithEndpoint); }); it('appendQueryParameters should append parameters correctly', () => { - assert.equal(util.appendQueryParameters(response.baseUrl, response.parameters), response.pathWithQuery); + assert.equal( + util.appendQueryParameters(response.baseUrl, response.parameters), + response.pathWithQuery, + ); }); it('isQueryParam should return the right boolean', () => { assert.equal(util.isQueryParam(response.parameters), true); @@ -97,17 +100,15 @@ describe('Utility function testing - Auth test returns 200 ok', () => { nock(baseUrl) .get('/auth/test') .reply(200, response.auth); - it('authenticate endpoint', () => { - return util.authenticate({ - baseUrl: baseUrl, - headers: { - authorization: 'Basic some_token', - }, - }) + it('authenticate endpoint', () => util.authenticate({ + baseUrl, + headers: { + authorization: 'Basic some_token', + }, + }) .then((res) => { assert.equal(res, response.auth.status === 200); - }); - }); + })); }); describe('Initial testing', () => { @@ -133,7 +134,6 @@ describe('Initial testing', () => { }); }); - describe('HTTP Request testing', () => { const onfleet = new Onfleet(apiKey); beforeEach(() => { @@ -181,133 +181,293 @@ describe('HTTP Request testing', () => { .reply(200, response.createCustomFields); }); - it('Get function', () => { - return onfleet.administrators.get() - .then((res) => { - expect(typeof res).to.equal('object'); - assert.equal(res[0].email, 'james@onfleet.com'); - assert.equal(res[0].type, 'super'); - assert.equal(res[1].email, 'wrapper@onfleet.com'); - assert.equal(res[1].type, 'standard'); - }); - }); + it('Get function', () => onfleet.administrators.get() + .then((res) => { + expect(typeof res).to.equal('object'); + assert.equal(res[0].email, 'james@onfleet.com'); + assert.equal(res[0].type, 'super'); + assert.equal(res[1].email, 'wrapper@onfleet.com'); + assert.equal(res[1].type, 'standard'); + })); - it('Get function - by ID', () => { - return onfleet.tasks.get('SxD9Ran6pOfnUDgfTecTsgXd') - .then((res) => { - expect(typeof res).to.equal('object'); - assert.equal(res.id, 'SxD9Ran6pOfnUDgfTecTsgXd'); - assert.equal(res.notes, 'Onfleet API Wrappers!'); - }); - }); + it('Get function - by ID', () => onfleet.tasks.get('SxD9Ran6pOfnUDgfTecTsgXd') + .then((res) => { + expect(typeof res).to.equal('object'); + assert.equal(res.id, 'SxD9Ran6pOfnUDgfTecTsgXd'); + assert.equal(res.notes, 'Onfleet API Wrappers!'); + })); - it('Get function - by ShortId', () => { - return onfleet.tasks.get('44a56188', 'shortId') - .then((res) => { - expect(typeof res).to.equal('object'); - assert.equal(res.shortId, '44a56188'); - assert.equal(res.trackingURL, 'https://onf.lt/44a56188'); - }); - }); + it('Get function - by ShortId', () => onfleet.tasks.get('44a56188', 'shortId') + .then((res) => { + expect(typeof res).to.equal('object'); + assert.equal(res.shortId, '44a56188'); + assert.equal(res.trackingURL, 'https://onf.lt/44a56188'); + })); - it('Get function - by phone number', () => { - return onfleet.recipients.get('+18881787788', 'phone') - .then((res) => { - expect(typeof res).to.equal('object'); - assert.equal(res.phone, '+18881787788'); - assert.equal(res.skipSMSNotifications, false); - }); - }); + it('Get function - by phone number', () => onfleet.recipients.get('+18881787788', 'phone') + .then((res) => { + expect(typeof res).to.equal('object'); + assert.equal(res.phone, '+18881787788'); + assert.equal(res.skipSMSNotifications, false); + })); - it('Get function - by name', () => { - return onfleet.recipients.get('Onfleet Rocks', 'name') - .then((res) => { - expect(typeof res).to.equal('object'); - assert.equal(res.name, 'Onfleet Rocks'); - }); - }); + it('Get function - by name', () => onfleet.recipients.get('Onfleet Rocks', 'name') + .then((res) => { + expect(typeof res).to.equal('object'); + assert.equal(res.name, 'Onfleet Rocks'); + })); - it('Create function', () => { - return onfleet.teams.create(newTeam) - .then((res) => { - expect(typeof res).to.equal('object'); - assert.equal(res.name, 'Onfleet Team'); - }); - }); + it('Create function', () => onfleet.teams.create(newTeam) + .then((res) => { + expect(typeof res).to.equal('object'); + assert.equal(res.name, 'Onfleet Team'); + })); - it('Get function - worker eta of team', () => { - return onfleet.teams.getWorkerEta('SxD9Ran6pOfnUDgfTecTsgXd', etaDetail) - .then((res) => { - expect(typeof res).to.equal('object'); - assert.equal(res.steps[0].arrivalTime, 1621339297); - }); - }); + it('Get function - worker eta of team', () => onfleet.teams.getWorkerEta('SxD9Ran6pOfnUDgfTecTsgXd', etaDetail) + .then((res) => { + expect(typeof res).to.equal('object'); + assert.equal(res.steps[0].arrivalTime, 1621339297); + })); - it('Force complete a task', () => { - return onfleet.tasks.forceComplete('6Fe3qqFZ0DDwsM86zBlHJtlJ', completionDetail) - .then((res) => { - expect(typeof res).to.equal('object'); - assert.equal(res.status, 200); - assert.equal(res.completionDetails.notes, 'Forced complete by Onfleet Wrapper'); - }); - }); + it('Force complete a task', () => onfleet.tasks.forceComplete('6Fe3qqFZ0DDwsM86zBlHJtlJ', completionDetail) + .then((res) => { + expect(typeof res).to.equal('object'); + assert.equal(res.status, 200); + assert.equal(res.completionDetails.notes, 'Forced complete by Onfleet Wrapper'); + })); - it('Update a worker', () => { - return onfleet.workers.update('Mdfs*NDZ1*lMU0abFXAT82lM', updateDetail) - .then((res) => { - expect(typeof res).to.equal('object'); - assert.equal(res.name, 'Stephen Curry'); - assert.equal(res.phone, '+18883033030'); - }); - }); + it('Update a worker', () => onfleet.workers.update('Mdfs*NDZ1*lMU0abFXAT82lM', updateDetail) + .then((res) => { + expect(typeof res).to.equal('object'); + assert.equal(res.name, 'Stephen Curry'); + assert.equal(res.phone, '+18883033030'); + })); - it('Delete a task', () => { - return onfleet.tasks.deleteOne('AqzN6ZAq*qlSDJ0FzmZIMZz~') - .then((res) => { - expect(typeof res).to.equal('number'); - assert.equal(res, 200); - }); - }); + it('Delete a task', () => onfleet.tasks.deleteOne('AqzN6ZAq*qlSDJ0FzmZIMZz~') + .then((res) => { + expect(typeof res).to.equal('number'); + assert.equal(res, 200); + })); - it('Get unassigned tasks in a team', () => { - return onfleet.teams.getTasks('K3FXFtJj2FtaO2~H60evRrDc') - .then((res) => { - expect(typeof res).to.equal('object'); - assert.equal(res.tasks.length, 1); - assert.equal(res.tasks[0].id, '3VtEMGudjwjjM60j7deSI123'); - }); - }); + it('Get unassigned tasks in a team', () => onfleet.teams.getTasks('K3FXFtJj2FtaO2~H60evRrDc') + .then((res) => { + expect(typeof res).to.equal('object'); + assert.equal(res.tasks.length, 1); + assert.equal(res.tasks[0].id, '3VtEMGudjwjjM60j7deSI123'); + })); - it('Get assigned tasks for a worker', () => { - return onfleet.workers.getTasks('ZxcnkJi~79nonYaMTQ960Mg2') - .then((res) => { - expect(typeof res).to.equal('object'); - assert.equal(res.tasks.length, 1); - assert.equal(res.tasks[0].id, '3VtEMGudjwjjM60j7deSI987'); - }); - }); + it('Get assigned tasks for a worker', () => onfleet.workers.getTasks('ZxcnkJi~79nonYaMTQ960Mg2') + .then((res) => { + expect(typeof res).to.equal('object'); + assert.equal(res.tasks.length, 1); + assert.equal(res.tasks[0].id, '3VtEMGudjwjjM60j7deSI987'); + })); - it('Get compliance information from tasks assigned to Onfleet drivers', () => { - return onfleet.workers.getDeliveryManifest(deliveryManifestObject) - .then((res) => { - expect(typeof res).to.equal('object'); - assert.equal(res.manifestDate, 1694199600000); - assert.equal(res.turnByTurn.length, 1); - }); - }); + it('Get compliance information from tasks assigned to Onfleet drivers', () => onfleet.workers.getDeliveryManifest(deliveryManifestObject) + .then((res) => { + expect(typeof res).to.equal('object'); + assert.equal(res.manifestDate, 1694199600000); + assert.equal(res.turnByTurn.length, 1); + })); + + it('Get custom fields', () => onfleet.customfields.get({ integration: 'shopify' }) + .then((res) => { + expect(typeof res).to.equal('object'); + assert.equal(res.fields.length, 1); + })); + + it('Create custom field', () => onfleet.customfields.create(createCustomField) + .then((res) => { + assert.equal(res, 200); + })); +}); + +describe('Additional resource coverage', () => { + const onfleet = new Onfleet(apiKey); + const hubId = 'tKxSfU7psqDQEBVn5e2VQ~*O'; + const destinationId = '9qcJpfoqLwDppaZO8wYPFfsT'; + const containerTeamId = 'K3FXFtJj2FtaO2~H60evRrDc'; + const routePlanId = 'aBcDeFgHiJkLmNoPqRsTuVwX'; + const webhookId = '2bcSv6Qd~OpvThCqkcdrq6w0'; + + // The Methods module uses a single, module-level Bottleneck limiter whose + // reservoir is only replenished when a response carries x-ratelimit-remaining. + // Returning it here keeps the shared reservoir topped up so the suite does not + // deplete it and start waiting (which would time the tests out). + const rateLimitHeaders = { 'x-ratelimit-remaining': '100' }; + + beforeEach(() => { + // Start from a clean slate so interceptors from previous suites do not leak. + nock.cleanAll(); + + // Hubs + nock(baseUrl) + .get((uri) => uri.includes('hubs')) + .reply(200, response.getHubs, rateLimitHeaders); + nock(baseUrl) + .post((uri) => uri.includes('hubs')) + .reply(200, response.createHub, rateLimitHeaders); + nock(baseUrl) + .put((uri) => uri.includes('hubs')) + .reply(200, response.updateHub, rateLimitHeaders); - it('Get custom fields', () => { - return onfleet.customfields.get({ integration: "shopify" }) - .then((res) => { - expect(typeof res).to.equal('object'); - assert.equal(res.fields.length, 1); - }); + // Destinations (metadata match registered first so it wins over plain create) + nock(baseUrl) + .post((uri) => uri.includes('destinations/metadata')) + .reply(200, response.matchDestinations, rateLimitHeaders); + nock(baseUrl) + .post((uri) => uri.includes('destinations')) + .reply(200, response.createDestination, rateLimitHeaders); + nock(baseUrl) + .get((uri) => uri.includes('destinations')) + .reply(200, response.getDestination, rateLimitHeaders); + + // Containers + nock(baseUrl) + .get((uri) => uri.includes('containers')) + .reply(200, response.getContainer, rateLimitHeaders); + + // Organization + nock(baseUrl) + .get((uri) => uri.includes('organization')) + .reply(200, response.getOrganization, rateLimitHeaders); + + // Route plans + nock(baseUrl) + .post((uri) => uri.includes('routePlans')) + .reply(200, response.createRoutePlan, rateLimitHeaders); + nock(baseUrl) + .get((uri) => uri.includes('routePlans')) + .reply(200, response.getRoutePlan, rateLimitHeaders); + nock(baseUrl) + .put((uri) => uri.includes('routePlans')) + .reply(200, response.updateRoutePlan, rateLimitHeaders); + nock(baseUrl) + .delete((uri) => uri.includes('routePlans')) + .reply(200, response.deleteTask, rateLimitHeaders); + + // Webhooks + nock(baseUrl) + .post((uri) => uri.includes('webhooks')) + .reply(200, response.createWebhook, rateLimitHeaders); + nock(baseUrl) + .get((uri) => uri.includes('webhooks')) + .reply(200, response.getWebhooks, rateLimitHeaders); + nock(baseUrl) + .delete((uri) => uri.includes('webhooks')) + .reply(200, response.deleteTask, rateLimitHeaders); }); - it('Create custom field', () => { - return onfleet.customfields.create(createCustomField) - .then((res) => { - assert.equal(res, 200); - }); + afterEach(() => { + nock.cleanAll(); }); + + // Hubs + it('Hubs - list hubs', () => onfleet.hubs.get() + .then((res) => { + expect(Array.isArray(res)).to.equal(true); + assert.equal(res[0].name, 'Downtown Hub'); + })); + + it('Hubs - create a hub', () => onfleet.hubs.create({ name: 'Downtown Hub' }) + .then((res) => { + expect(typeof res).to.equal('object'); + assert.equal(res.id, hubId); + assert.equal(res.name, 'Downtown Hub'); + })); + + it('Hubs - update a hub', () => onfleet.hubs.update(hubId, { name: 'Uptown Hub' }) + .then((res) => { + expect(typeof res).to.equal('object'); + assert.equal(res.name, 'Uptown Hub'); + })); + + // Destinations + it('Destinations - create a destination', () => onfleet.destinations.create({ + address: { number: '543', street: 'Howard Street', city: 'San Francisco' }, + }) + .then((res) => { + expect(typeof res).to.equal('object'); + assert.equal(res.id, destinationId); + assert.equal(res.address.street, 'Howard Street'); + })); + + it('Destinations - get a destination by ID', () => onfleet.destinations.get(destinationId) + .then((res) => { + expect(typeof res).to.equal('object'); + assert.equal(res.id, destinationId); + })); + + it('Destinations - match metadata', () => onfleet.destinations.matchMetadata([ + { name: 'hello', type: 'string', value: 'world' }, + ]) + .then((res) => { + expect(Array.isArray(res)).to.equal(true); + assert.equal(res[0].id, destinationId); + })); + + // Containers + it('Containers - get a container by team', () => onfleet.containers.get(containerTeamId, 'teams') + .then((res) => { + expect(typeof res).to.equal('object'); + assert.equal(res.type, 'TEAM'); + assert.equal(res.team, containerTeamId); + })); + + // Organization + it('Organization - get own organization', () => onfleet.organization.get() + .then((res) => { + expect(typeof res).to.equal('object'); + assert.equal(res.name, 'Onfleet Organization'); + assert.equal(res.country, 'US'); + })); + + // Route plans + it('Route plans - create a route plan', () => onfleet.routeplan.create({ name: 'My Route Plan' }) + .then((res) => { + expect(typeof res).to.equal('object'); + assert.equal(res.id, routePlanId); + assert.equal(res.state, 'PENDING'); + })); + + it('Route plans - get a route plan by ID', () => onfleet.routeplan.get(routePlanId) + .then((res) => { + expect(typeof res).to.equal('object'); + assert.equal(res.id, routePlanId); + assert.equal(res.state, 'PLANNED'); + })); + + it('Route plans - update a route plan', () => onfleet.routeplan.update(routePlanId, { name: 'Updated Route Plan' }) + .then((res) => { + expect(typeof res).to.equal('object'); + assert.equal(res.name, 'Updated Route Plan'); + })); + + it('Route plans - delete a route plan', () => onfleet.routeplan.deleteOne(routePlanId) + .then((res) => { + expect(typeof res).to.equal('number'); + assert.equal(res, 200); + })); + + // Webhooks + it('Webhooks - list webhooks', () => onfleet.webhooks.get() + .then((res) => { + expect(Array.isArray(res)).to.equal(true); + assert.equal(res[0].id, webhookId); + })); + + it('Webhooks - create a webhook', () => onfleet.webhooks.create({ + url: 'https://www.example.com/onfleet', trigger: 0, + }) + .then((res) => { + expect(typeof res).to.equal('object'); + assert.equal(res.id, webhookId); + assert.equal(res.isEnabled, true); + })); + + it('Webhooks - delete a webhook', () => onfleet.webhooks.deleteOne(webhookId) + .then((res) => { + expect(typeof res).to.equal('number'); + assert.equal(res, 200); + })); });