diff --git a/.npmignore b/.npmignore index f5efe921a..23e3eb5e3 100644 --- a/.npmignore +++ b/.npmignore @@ -7,7 +7,6 @@ !/dist/**/* !/LICENSE !/package.json -!/prop_types.js !/README.md !/src/**/* !/UPGRADE_GUIDE.md diff --git a/README.md b/README.md index afac0398e..0b6088488 100644 --- a/README.md +++ b/README.md @@ -14,52 +14,8 @@ NOTE: This library is intended to be used in a backend Node service and not in a npm install --save @easypost/api ``` -**NOTE:** If you are using @easypost/api prior to v5 and a version of Node less than 6.9, you will need to install and include a polyfill, such as `babel-polyfill`, and include it in your project: - -```bash -npm install --save babel-polyfill -``` - -```javascript -// Require the polyfill if necessary: -require('babel-polyfill'); - -// Require the EasyPost library: -const EasyPost = require('@easypost/api'); -``` - You can alternatively download the various built assets from this project's [releases page](https://github.com/EasyPost/easypost-node/releases). -### Compatibility - -#### v4 and Earlier - -- `require('@easypost/api/easypost.8-lts.js')` (Node 8.9+) -- `require('@easypost/api/easypost.6-lts.js')` (Node 6.9+) -- `require('@easypost/api/easypost.legacy.js')` (Node 0.10+) - -#### v5 - -If using @easypost/api v5, you can require the base project which is built on Node v10 - v16 - -#### v6 - -If using @easypost/api v6, you can require the base project which is built on Node v12+ - -### Note on ES6 Usage - -You can import specific versions of the compiled code if you're using later versions of Node and using @easypost/api prior to v5. - -```javascript -// Imports the un-transformed es6 -import '@easypost/api/src/easypost'; - -// Use the following to import mininally transformed versions -import '@easypost/api/easypost.6-lts'; -import '@easypost/api/easypost.8-lts'; -import '@easypost/api/easypost.legacy.js'; // (v0.10) -``` - ## Usage A simple create & buy shipment example: diff --git a/package-lock.json b/package-lock.json index a4bc73dd0..414973890 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,12 +20,9 @@ "@typescript-eslint/parser": "^8.68.0", "@vitest/coverage-istanbul": "^4.0.18", "audit-ci": "^7.1", - "chai": "^5.2", - "chai-as-promised": "^8.0.1", "cross-env": "^7.0.3", "eslint": "^8.57.1", "eslint-config-prettier": "^8.8.0", - "eslint-plugin-vitest-globals": "^1.5.0", "jsdoc": "^4.0.5", "prettier": "^3.8", "typescript": "~5.3.0", @@ -2384,36 +2381,6 @@ "node": ">= 10" } }, - "node_modules/chai": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", - "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/chai-as-promised": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-8.0.2.tgz", - "integrity": "sha512-1GadL+sEJVLzDjcawPM4kjfnL+p/9vrxiEUonowKOAzvVg0PixJUdtuDzdkDeQhK3zfOE76GqGkZIQ7/Adcrqw==", - "dev": true, - "license": "MIT", - "dependencies": { - "check-error": "^2.1.1" - }, - "peerDependencies": { - "chai": ">= 2.1.2 < 7" - } - }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2431,16 +2398,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/check-error": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", - "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16" - } - }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -2600,16 +2557,6 @@ } } }, - "node_modules/deep-eql": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -2899,20 +2846,6 @@ "eslint": ">=7.0.0" } }, - "node_modules/eslint-plugin-vitest-globals": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-vitest-globals/-/eslint-plugin-vitest-globals-1.6.1.tgz", - "integrity": "sha512-ydMvr96XRaT913a8gy8TY/LLyGB49Wmj0+x/FCbaWSpWMSaWDLG63/5zSLLrdSGXJ0PTjWpYZOZ61YBuzNyyTw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/saqqdy" - } - }, "node_modules/eslint-scope": { "version": "7.2.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", @@ -4088,13 +4021,6 @@ "url": "https://tidelift.com/funding/github/npm/loglevel" } }, - "node_modules/loupe": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", - "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", - "dev": true, - "license": "MIT" - }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -4602,16 +4528,6 @@ "dev": true, "license": "MIT" }, - "node_modules/pathval": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", - "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.16" - } - }, "node_modules/pause-stream": { "version": "0.0.11", "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", diff --git a/package.json b/package.json index 7f1887902..f32de1edb 100644 --- a/package.json +++ b/package.json @@ -54,12 +54,9 @@ "@typescript-eslint/parser": "^8.68.0", "@vitest/coverage-istanbul": "^4.0.18", "audit-ci": "^7.1", - "chai": "^5.2", - "chai-as-promised": "^8.0.1", "cross-env": "^7.0.3", "eslint": "^8.57.1", "eslint-config-prettier": "^8.8.0", - "eslint-plugin-vitest-globals": "^1.5.0", "jsdoc": "^4.0.5", "prettier": "^3.8", "typescript": "~5.3.0", diff --git a/test/.eslintrc b/test/.eslintrc index 5bbbaf11f..ea0633659 100644 --- a/test/.eslintrc +++ b/test/.eslintrc @@ -1,19 +1,36 @@ { - "extends": ["eslint:recommended", "prettier", "plugin:vitest-globals/recommended"], + "extends": ["eslint:recommended", "prettier"], "env": { "node": true, - "vitest-globals/env": true + "es2021": true, }, "rules": { - "global-require": "off" + "global-require": "off", }, "globals": { - "expect": true + "afterAll": true, + "afterEach": true, + "beforeAll": true, + "beforeEach": true, + "describe": true, + "expect": true, + "it": true, + "vi": true, }, + "overrides": [ + { + "files": ["**/*.ts"], + "parser": "@typescript-eslint/parser", + "rules": { + "no-undef": "off", + "no-unused-vars": "off", + }, + }, + ], "parserOptions": { - "ecmaVersion": 13 - } + "ecmaVersion": 13, + }, } diff --git a/test/helpers/fixture.d.ts b/test/helpers/fixture.d.ts deleted file mode 100644 index 90ac5fb5b..000000000 --- a/test/helpers/fixture.d.ts +++ /dev/null @@ -1,62 +0,0 @@ -import type AddressServiceFactory from '../../src/services/address_service'; -import type ParcelServiceFactory from '../../src/services/parcel_service'; -import type CustomsInfoServiceFactory from '../../src/services/customs_info_service'; -import type CustomsItemServiceFactory from '../../src/services/customs_item_service'; -import type ShipmentServiceFactory from '../../src/services/shipment_service'; - -type AddressCreateInput = Parameters['create']>[0]; -type ParcelCreateInput = Parameters['create']>[0]; -type CustomsInfoCreateInput = Parameters['create']>[0]; -type CustomsItemCreateInput = Parameters['create']>[0]; -type ShipmentCreateInput = Parameters['create']>[0]; - -declare class Fixture { - static readFixtureData(): Record; - static pageSize(): number; - - static uspsCarrierAccountId(): string; - static usps(): string; - static uspsService(): string; - static pickupService(): string; - static reportType(): string; - static reportDate(): string; - - static caAddress1(): AddressCreateInput; - static caAddress2(): AddressCreateInput; - static incorrectAddress(): AddressCreateInput; - - static basicParcel(): ParcelCreateInput; - static basicCustomsItem(): CustomsItemCreateInput; - static basicCustomsInfo(): CustomsInfoCreateInput; - static taxIdentifier(): Record; - - static basicShipment(): ShipmentCreateInput; - static fullShipment(): ShipmentCreateInput; - static oneCallBuyShipment(): ShipmentCreateInput & Record; - - static basicPickup(): Record; - static basicCarrierAccount(): Record; - static basicInsurance(): Record; - static basicClaim(): Record; - static basicOrder(): Record; - - static creditCardDetails(): Record; - static rmaFormOptions(): Record; - - static eventBody(): Buffer; - static webhookHmacSignature(): string; - static webhookSecret(): string; - static webhookUrl(): string; - static webhookCustomHeaders(): Record; - - static plannedShipDate(): string; - static plannedDeliveryDate(): string; - static billing(): Record; - - static lumaRulesetName(): string; - static lumaPlannedShipDate(): string; - - static referralUser(): Record; -} - -export default Fixture; diff --git a/test/helpers/fixture.js b/test/helpers/fixture.js deleted file mode 100644 index ca812a169..000000000 --- a/test/helpers/fixture.js +++ /dev/null @@ -1,181 +0,0 @@ -import fs from 'fs'; - -export default class Fixture { - // Read fixture data from the fixtures JSON file - static readFixtureData() { - const currentDir = process.cwd(); - const fixtures = JSON.parse( - fs.readFileSync(`${currentDir}/examples/official/fixtures/client-library-fixtures.json`), - ); - - return fixtures; - } - - // We keep the page_size of retrieving `all` records small so cassettes stay small - static pageSize() { - return this.readFixtureData().page_sizes.five_results; - } - - // This is the USPS carrier account ID that comes with your EasyPost account by default and should be used for all tests - static uspsCarrierAccountId() { - // Fallback to the EasyPost Node Client Library Test User USPS carrier account ID due to strict matching - return process.env.USPS_CARRIER_ACCOUNT_ID || 'ca_5ba7ca3632c54adeb17ad4bcac13c890'; - } - - static usps() { - return this.readFixtureData().carrier_strings.usps; - } - - static uspsService() { - return this.readFixtureData().service_names.usps.first_service; - } - - static pickupService() { - return this.readFixtureData().service_names.usps.pickup_service; - } - - static reportType() { - return this.readFixtureData().report_types.shipment; - } - - static reportDate() { - return '2026-07-30'; - } - - static caAddress1() { - return this.readFixtureData().addresses.ca_address_1; - } - - static caAddress2() { - return this.readFixtureData().addresses.ca_address_2; - } - - static incorrectAddress() { - return this.readFixtureData().addresses.incorrect; - } - - static basicParcel() { - return this.readFixtureData().parcels.basic; - } - - static basicCustomsItem() { - return this.readFixtureData().customs_items.basic; - } - - static basicCustomsInfo() { - return this.readFixtureData().customs_infos.basic; - } - - static taxIdentifier() { - return this.readFixtureData().tax_identifiers.basic; - } - - static basicShipment() { - return this.readFixtureData().shipments.basic_domestic; - } - - static fullShipment() { - return this.readFixtureData().shipments.full; - } - - static oneCallBuyShipment() { - return { - to_address: this.caAddress2(), - from_address: this.caAddress1(), - parcel: this.basicParcel(), - service: this.uspsService(), - carrier_accounts: [this.uspsCarrierAccountId()], - carrier: this.usps(), - }; - } - - // This fixture will require you to add a `shipment` key with a Shipment object from a test. - // If you need to re-record cassettes, increment the date below and ensure it is one day in the future, - // USPS only does "next-day" pickups including Saturday but not Sunday or Holidays. - static basicPickup() { - const pickupDate = '2026-08-01'; - - const pickupData = this.readFixtureData().pickups.basic; - pickupData.min_datetime = pickupDate; - pickupData.max_datetime = pickupDate; - - return pickupData; - } - - static basicCarrierAccount() { - return this.readFixtureData().carrier_accounts.basic; - } - - // This fixture will require you to append a `tracking_code` key with the shipment's tracking code - static basicInsurance() { - return this.readFixtureData().insurances.basic; - } - - static basicClaim() { - return this.readFixtureData().claims.basic; - } - - static basicOrder() { - return this.readFixtureData().orders.basic; - } - - // The credit card details below are for a valid proxy card usable - // for tests only and cannot be used for real transactions. - // DO NOT alter these details with real credit card information. - static creditCardDetails() { - return this.readFixtureData().credit_cards.test; - } - - static rmaFormOptions() { - return this.readFixtureData().form_options.rma; - } - - static eventBody() { - const currentDir = process.cwd(); - const eventBody = JSON.parse( - fs.readFileSync(`${currentDir}/examples/official/fixtures/event-body.json`), - ); - - return Buffer.from(JSON.stringify(eventBody), 'utf8'); - } - - static webhookHmacSignature() { - return this.readFixtureData().webhooks.hmac_signature; - } - - static webhookSecret() { - return this.readFixtureData().webhooks.secret; - } - - static webhookUrl() { - return this.readFixtureData().webhooks.url; - } - - static webhookCustomHeaders() { - return this.readFixtureData().webhooks.custom_headers; - } - - static plannedShipDate() { - return '2026-08-01'; - } - - static plannedDeliveryDate() { - return '2026-08-03'; - } - - static billing() { - return this.readFixtureData().billing; - } - - static lumaRulesetName() { - return this.readFixtureData().luma.ruleset_name; - } - - static lumaPlannedShipDate() { - return '2026-08-01'; - } - - static referralUser() { - return this.readFixtureData().users.referral; - } -} diff --git a/test/helpers/fixture.ts b/test/helpers/fixture.ts new file mode 100644 index 000000000..0558e518a --- /dev/null +++ b/test/helpers/fixture.ts @@ -0,0 +1,214 @@ +import fs from 'fs'; + +type FixtureData = Record; + +export default class Fixture { + // Read fixture data from the fixtures JSON file. + static readFixtureData(): FixtureData { + const currentDir = process.cwd(); + const fixtures = JSON.parse( + fs.readFileSync( + `${currentDir}/examples/official/fixtures/client-library-fixtures.json`, + 'utf8', + ), + ) as FixtureData; + + return fixtures; + } + + // We keep the page_size of retrieving `all` records small so cassettes stay small. + static pageSize(): number { + const fixtureData = this.readFixtureData() as Record; + return fixtureData.page_sizes.five_results as number; + } + + // This is the USPS carrier account ID that comes with your EasyPost account by default and should be used for all tests. + static uspsCarrierAccountId(): string { + // Fallback to the EasyPost Node Client Library Test User USPS carrier account ID due to strict matching. + return process.env.USPS_CARRIER_ACCOUNT_ID || 'ca_5ba7ca3632c54adeb17ad4bcac13c890'; + } + + static usps(): string { + const fixtureData = this.readFixtureData() as Record; + return fixtureData.carrier_strings.usps as string; + } + + static uspsService(): string { + const fixtureData = this.readFixtureData() as Record; + return fixtureData.service_names.usps.first_service as string; + } + + static pickupService(): string { + const fixtureData = this.readFixtureData() as Record; + return fixtureData.service_names.usps.pickup_service as string; + } + + static reportType(): string { + const fixtureData = this.readFixtureData() as Record; + return fixtureData.report_types.shipment as string; + } + + static reportDate(): string { + return '2026-07-30'; + } + + static caAddress1(): Record { + const fixtureData = this.readFixtureData() as Record; + return fixtureData.addresses.ca_address_1 as Record; + } + + static caAddress2(): Record { + const fixtureData = this.readFixtureData() as Record; + return fixtureData.addresses.ca_address_2 as Record; + } + + static incorrectAddress(): Record { + const fixtureData = this.readFixtureData() as Record; + return fixtureData.addresses.incorrect as Record; + } + + static basicParcel(): Record { + const fixtureData = this.readFixtureData() as Record; + return fixtureData.parcels.basic as Record; + } + + static basicCustomsItem(): Record { + const fixtureData = this.readFixtureData() as Record; + return fixtureData.customs_items.basic as Record; + } + + static basicCustomsInfo(): Record { + const fixtureData = this.readFixtureData() as Record; + return fixtureData.customs_infos.basic as Record; + } + + static taxIdentifier(): Record { + const fixtureData = this.readFixtureData() as Record; + return fixtureData.tax_identifiers.basic as Record; + } + + static basicShipment(): Record { + const fixtureData = this.readFixtureData() as Record; + return fixtureData.shipments.basic_domestic as Record; + } + + static fullShipment(): Record { + const fixtureData = this.readFixtureData() as Record; + return fixtureData.shipments.full as Record; + } + + static oneCallBuyShipment(): Record { + return { + to_address: this.caAddress2(), + from_address: this.caAddress1(), + parcel: this.basicParcel(), + service: this.uspsService(), + carrier_accounts: [this.uspsCarrierAccountId()], + carrier: this.usps(), + }; + } + + // This fixture will require you to add a `shipment` key with a Shipment object from a test. + // If you need to re-record cassettes, increment the date below and ensure it is one day in the future, + // USPS only does "next-day" pickups including Saturday but not Sunday or Holidays. + static basicPickup(): Record { + const pickupDate = '2026-08-01'; + + const fixtureData = this.readFixtureData() as Record; + const pickupData = fixtureData.pickups.basic as Record; + pickupData.min_datetime = pickupDate; + pickupData.max_datetime = pickupDate; + + return pickupData; + } + + static basicCarrierAccount(): Record { + const fixtureData = this.readFixtureData() as Record; + return fixtureData.carrier_accounts.basic as Record; + } + + // This fixture will require you to append a `tracking_code` key with the shipment's tracking code. + static basicInsurance(): Record { + const fixtureData = this.readFixtureData() as Record; + return fixtureData.insurances.basic as Record; + } + + static basicClaim(): Record { + const fixtureData = this.readFixtureData() as Record; + return fixtureData.claims.basic as Record; + } + + static basicOrder(): Record { + const fixtureData = this.readFixtureData() as Record; + return fixtureData.orders.basic as Record; + } + + // The credit card details below are for a valid proxy card usable + // for tests only and cannot be used for real transactions. + // DO NOT alter these details with real credit card information. + static creditCardDetails(): Record { + const fixtureData = this.readFixtureData() as Record; + return fixtureData.credit_cards.test as Record; + } + + static rmaFormOptions(): Record { + const fixtureData = this.readFixtureData() as Record; + return fixtureData.form_options.rma as Record; + } + + static eventBody(): Buffer { + const currentDir = process.cwd(); + const eventBody = JSON.parse( + fs.readFileSync(`${currentDir}/examples/official/fixtures/event-body.json`, 'utf8'), + ); + + return Buffer.from(JSON.stringify(eventBody), 'utf8'); + } + + static webhookHmacSignature(): string { + const fixtureData = this.readFixtureData() as Record; + return fixtureData.webhooks.hmac_signature as string; + } + + static webhookSecret(): string { + const fixtureData = this.readFixtureData() as Record; + return fixtureData.webhooks.secret as string; + } + + static webhookUrl(): string { + const fixtureData = this.readFixtureData() as Record; + return fixtureData.webhooks.url as string; + } + + static webhookCustomHeaders(): Record { + const fixtureData = this.readFixtureData() as Record; + return fixtureData.webhooks.custom_headers as Record; + } + + static plannedShipDate(): string { + return '2026-08-01'; + } + + static plannedDeliveryDate(): string { + return '2026-08-03'; + } + + static billing(): Record { + const fixtureData = this.readFixtureData() as Record; + return fixtureData.billing as Record; + } + + static lumaRulesetName(): string { + const fixtureData = this.readFixtureData() as Record; + return fixtureData.luma.ruleset_name as string; + } + + static lumaPlannedShipDate(): string { + return '2026-08-01'; + } + + static referralUser(): Record { + const fixtureData = this.readFixtureData() as Record; + return fixtureData.users.referral as Record; + } +} diff --git a/test/helpers/mocking.js b/test/helpers/mocking.ts similarity index 61% rename from test/helpers/mocking.js rename to test/helpers/mocking.ts index 49dcd9344..6d3b3ab30 100644 --- a/test/helpers/mocking.js +++ b/test/helpers/mocking.ts @@ -1,38 +1,57 @@ +type MockRequestLike = { + method: string; + url: string; +}; + export class MockRequestMatchRule { - constructor(method, urlRegexPattern) { + method: string; + urlRegexPattern: RegExp; + + constructor(method: string, urlRegexPattern: string | RegExp) { this.method = method; - this.urlRegexPattern = urlRegexPattern; + this.urlRegexPattern = + urlRegexPattern instanceof RegExp ? urlRegexPattern : new RegExp(urlRegexPattern); } } export class MockRequestResponseInfo { - constructor(statusCode, responseData) { + statusCode: number; + body: unknown; + + constructor(statusCode: number, responseData: unknown) { this.statusCode = statusCode; this.body = responseData; } } export class MockRequest { - constructor(matchRule, responseInfo) { + matchRule: MockRequestMatchRule; + responseInfo: MockRequestResponseInfo; + + constructor(matchRule: MockRequestMatchRule, responseInfo: MockRequestResponseInfo) { this.matchRule = matchRule; this.responseInfo = responseInfo; } } export class MockMiddleware { - constructor(request, mockRequests) { + request: MockRequestLike; + mockRequests: MockRequest[]; + body: unknown; + + constructor(request: MockRequestLike, mockRequests: MockRequest[]) { this.request = request; this.mockRequests = mockRequests; this.body = {}; } // eslint-disable-next-line class-methods-use-this,no-unused-vars - auth(key) { + auth(_key: string) { // do nothing } // eslint-disable-next-line class-methods-use-this,no-unused-vars,consistent-return - send(body) { + send(_body: unknown): any { // we don't need to do anything with the body const mockRequest = this._findMatchingMockRequest(this.request); if (mockRequest) { @@ -43,7 +62,7 @@ export class MockMiddleware { } // eslint-disable-next-line no-unused-vars - query(params) { + query(_params: unknown): any { // we don't need to do anything with the params const mockRequest = this._findMatchingMockRequest(this.request); if (mockRequest) { @@ -56,8 +75,8 @@ export class MockMiddleware { /** * @private */ - _findMatchingMockRequest(request) { - // for each mock request, check if the method and url match + _findMatchingMockRequest(request: MockRequestLike): MockRequest | null { + // For each mock request, check if the method and URL match. for (let i = 0; i < this.mockRequests.length; i += 1) { const mockRequest = this.mockRequests[i]; if ( diff --git a/test/helpers/setup_polly.js b/test/helpers/setup_polly.ts similarity index 57% rename from test/helpers/setup_polly.js rename to test/helpers/setup_polly.ts index bb6dd65e8..ac4e368c8 100644 --- a/test/helpers/setup_polly.js +++ b/test/helpers/setup_polly.ts @@ -2,6 +2,43 @@ import FetchAdapter from '@pollyjs/adapter-fetch'; import { Polly } from '@pollyjs/core'; import FSPersister from '@pollyjs/persister-fs'; import { resolve } from 'path'; +import { afterEach, beforeEach } from 'vitest'; + +type PollyRequest = { + hasHeader(name: string): boolean; + setHeader(name: string, value: string | number): void; + hostname?: string; + body?: unknown; +}; + +type PollyHeader = { name: string }; + +type PollyRecording = { + request: { + headers: PollyHeader[]; + }; + response: { + content: { + text?: string; + encoding?: string; + }; + }; +}; + +type PollyServer = { + any(): { + on(event: string, handler: (...args: unknown[]) => void): void; + }; +}; + +type VitestTaskContext = { + task?: { + suite?: { + name?: string; + }; + name: string; + }; +}; Polly.register(FSPersister); Polly.register(FetchAdapter); @@ -36,7 +73,7 @@ normalizeNavigatorOnlineForNode(); const redactedString = ''; const redactedObject = {}; -const redactedArray = []; +const redactedArray: unknown[] = []; const scrubbers = { client_ip: redactedString, credentials: redactedObject, @@ -48,7 +85,7 @@ const scrubbers = { test_credentials: redactedObject, }; -function scrubHeaders(recording) { +function scrubHeaders(recording: PollyRecording) { recording.request.headers = recording.request.headers.filter( ({ name }) => !headerScrubbers.includes(name), ); @@ -56,57 +93,55 @@ function scrubHeaders(recording) { /** * Scrub individual element data of a cassette. - * @param {*} data - * @param {*} scrubberEntry */ -function scrubData(data, scrubberEntry) { +function scrubData(data: unknown, scrubberEntry: [string, unknown]): unknown { const [key, replacement] = scrubberEntry; - // Root-level list scrubbing if (Array.isArray(data)) { - data.map((item, index) => { - if (item[key]) { - data[index][key] = replacement; + return data.map((item) => scrubData(item, scrubberEntry)); + } + + if (typeof data === 'object' && data !== null) { + const input = data as Record; + const output: Record = { ...input }; + + if (Object.prototype.hasOwnProperty.call(output, key)) { + output[key] = replacement; + } + + for (const item of Object.keys(output)) { + const element = output[item]; + if (Array.isArray(element)) { + output[item] = element.map((nestedItem) => scrubData(nestedItem, scrubberEntry)); + } else if (typeof element === 'object' && element !== null) { + output[item] = scrubData(element, scrubberEntry); } - }); - } else if (typeof data === 'object' && data !== null) { - // Root-level key scrubbing - if (data[key]) { - data[key] = replacement; - } else { - // Nested scrubbing - Object.keys(data).forEach((item) => { - const element = data[item]; - if (Array.isArray(element)) { - element.map((nestedItem, nestedIndex) => { - data[item][nestedIndex] = scrubData(nestedItem, scrubberEntry); - }); - } else if (typeof element === 'object' && element !== null) { - data[item] = scrubData(element, scrubberEntry); - } - }); } + + return output; } return data; } // Scrub sensitive data from response bodies prior to recording the cassette. -function scrubResponseBodies(recording) { - let response = recording.response.content.text; +function scrubResponseBodies(recording: PollyRecording) { + const response = recording.response.content.text; - if (response) { - const responseBody = JSON.parse(response); + if (!response) { + return; + } - Object.entries(scrubbers).forEach((scrubberEntry) => { - response = scrubData(responseBody, scrubberEntry); - }); + let responseBody = JSON.parse(response) as unknown; + + for (const scrubberEntry of Object.entries(scrubbers)) { + responseBody = scrubData(responseBody, scrubberEntry); } - recording.response.content.text = JSON.stringify(response); + recording.response.content.text = JSON.stringify(responseBody); } -function isJsonString(value) { +function isJsonString(value: unknown) { if (typeof value !== 'string') { return false; } @@ -124,7 +159,7 @@ function isJsonString(value) { } } -function normalizeLegacyReplayEncoding(recording) { +function normalizeLegacyReplayEncoding(recording: PollyRecording) { const content = recording?.response?.content; // Some legacy cassettes store plain JSON text but still mark `encoding: base64`. @@ -134,25 +169,29 @@ function normalizeLegacyReplayEncoding(recording) { } } -function setupCassette(server) { - server.any().on('beforePersist', (_, recording) => { +function setupCassette(server: PollyServer) { + server.any().on('beforePersist', (_, rec) => { // TODO: Add support to scrub CC details from the request URL and `queryParams` + const recording = rec as PollyRecording; scrubHeaders(recording); try { scrubResponseBodies(recording); } catch (err) { - throw new Error(`Error scrubbing cassette: ${err.message}`); + const message = err instanceof Error ? err.message : String(err); + throw new Error(`Error scrubbing cassette: ${message}`); } }); - server.any().on('beforeReplay', (_, recording) => { - normalizeLegacyReplayEncoding(recording); + server.any().on('beforeReplay', (_, rec) => { + normalizeLegacyReplayEncoding(rec as PollyRecording); }); } -function setupLegacyRequestIdentityCompatibility(server) { - server.any().on('request', (req) => { +function setupLegacyRequestIdentityCompatibility(server: PollyServer) { + server.any().on('request', (reqArg) => { + const req = reqArg as PollyRequest; + // Keep request identifiers compatible with pre-fetch cassettes. if (!req.hasHeader('accept-encoding')) { req.setHeader('accept-encoding', legacyIdentityHeaders['accept-encoding']); @@ -168,15 +207,15 @@ function setupLegacyRequestIdentityCompatibility(server) { }); } -// New setup function for Vitest +// New setup function for Vitest. function setupPollyTests() { - /** @type {Polly} */ - let polly; + let polly: Polly; const recordingsDir = resolve(__dirname, '../cassettes'); - beforeEach((context) => { + beforeEach((context: VitestTaskContext) => { const suiteName = context.task?.suite?.name || 'unknown-suite'; - const recordingName = `${suiteName}/${context.task.name}`; + const taskName = context.task?.name || 'unknown-task'; + const recordingName = `${suiteName}/${taskName}`; polly = new Polly(recordingName, { adapters: ['fetch'], @@ -196,7 +235,7 @@ function setupPollyTests() { expiryStrategy: 'warn', }); - setupLegacyRequestIdentityCompatibility(polly.server); + setupLegacyRequestIdentityCompatibility(polly.server as unknown as PollyServer); }); afterEach(async () => { diff --git a/test/helpers/utils.js b/test/helpers/utils.js deleted file mode 100644 index 3c0ad3987..000000000 --- a/test/helpers/utils.js +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Utils to remove the _params key and all params keys from nested objects from - * an EasyPostObject for sake of comparison in tests - * @param {*} obj an EasyPostObject to remove the params from - * @returns {*} obj without the _params key - */ -export const withoutParams = (obj) => - Object.fromEntries( - Object.entries(obj) - .map(([key, value]) => { - if (key === '_params') { - return null; - } - - if (!value) { - return [key, value]; - } - - if (Array.isArray(value)) { - return [ - key, - value.map((arrValue) => - typeof arrValue === 'object' ? withoutParams(arrValue) : arrValue, - ), - ]; - } - - if (typeof value === 'object') { - return [key, withoutParams(value)]; - } - - return [key, value]; - }) - .filter(Boolean), - ); diff --git a/test/helpers/utils.ts b/test/helpers/utils.ts new file mode 100644 index 000000000..5995ae1a5 --- /dev/null +++ b/test/helpers/utils.ts @@ -0,0 +1,41 @@ +/** + * Remove `_params` keys recursively for object comparison in tests. + */ +export const withoutParams = (obj: T): T => { + if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) { + return obj; + } + + const input = obj as Record; + const entries: Array<[string, unknown]> = []; + + for (const [key, value] of Object.entries(input)) { + if (key === '_params') { + continue; + } + + if (!value) { + entries.push([key, value]); + continue; + } + + if (Array.isArray(value)) { + entries.push([ + key, + value.map((arrValue) => + typeof arrValue === 'object' && arrValue !== null ? withoutParams(arrValue) : arrValue, + ), + ]); + continue; + } + + if (typeof value === 'object') { + entries.push([key, withoutParams(value)]); + continue; + } + + entries.push([key, value]); + } + + return Object.fromEntries(entries) as T; +}; diff --git a/tsconfig.base.json b/tsconfig.base.json index a62fb87aa..736811807 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1,9 +1,8 @@ { "compilerOptions": { - "target": "es5", + "target": "es2022", "module": "nodenext", "strict": true, - "ignoreDeprecations": "5.0", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "skipLibCheck": true diff --git a/tsconfig.build.json b/tsconfig.build.json index c0cd3fcad..b66771e26 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -1,13 +1,10 @@ { "extends": "./tsconfig.base.json", "compilerOptions": { - "allowJs": true, - "checkJs": false, "noImplicitAny": true, - "declaration": true, "noEmit": true, - "types": ["vitest/globals", "node"] + "types": ["node"] }, - "include": ["src/**/*.js", "src/**/*.ts", "test/**/*.js", "test/**/*.ts"], + "include": ["src/**/*.ts"], "exclude": ["dist/**", "docs/**", "coverage/**", "node_modules/**"] } diff --git a/tsconfig.json b/tsconfig.json index 1b3262cab..6f1808a0c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,8 +1,6 @@ { "extends": "./tsconfig.base.json", "compilerOptions": { - "allowJs": true, - "checkJs": false, "noImplicitAny": true, "declaration": true, "emitDeclarationOnly": true, @@ -10,6 +8,6 @@ "outDir": "./dist/types", "noEmit": false }, - "include": ["src/**/*.js", "src/**/*.ts"], + "include": ["src/**/*.ts"], "exclude": ["dist/**", "docs/**", "coverage/**", "node_modules/**"] } diff --git a/tsconfig.test-services.json b/tsconfig.test-services.json index befba3e02..dc3347e12 100644 --- a/tsconfig.test-services.json +++ b/tsconfig.test-services.json @@ -1,8 +1,6 @@ { "extends": "./tsconfig.base.json", "compilerOptions": { - "allowJs": true, - "checkJs": false, "noImplicitAny": true, "noEmit": true, "skipLibCheck": true, diff --git a/vitest.config.js b/vitest.config.js index 44d770dc5..0193f01d1 100644 --- a/vitest.config.js +++ b/vitest.config.js @@ -5,11 +5,11 @@ export default defineConfig({ globals: true, environment: 'node', testTimeout: 10000, - include: ['test/services/**/*.{js,ts}'], + include: ['test/services/**/*.ts'], coverage: { provider: 'istanbul', reporter: ['text', 'lcov', 'html'], - include: ['src/**/*.{js,ts}'], + include: ['src/**/*.ts'], thresholds: { lines: 87, },