diff --git a/CHANGELOG.md b/CHANGELOG.md index a80ed46a..de877c02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# 3.0.0 (10 Sep 2024) + +- Added `adapter` property to `XAPI` constructor params, allowing a choice between `axios` (Default), `fetch` or a custom adapter function +- Adds support for edge environments through the `fetch` adapter +- Updated `axios` to patch security vulnerability ([CVE-2024-39338](https://github.com/advisories/GHSA-8hc4-vh64-cxmj)) + # 2.2.8 (31 Jul 2024) - Updated `braces` to patch security vulnerability diff --git a/jest.config.edge.js b/jest.config.edge.js new file mode 100644 index 00000000..6117ae90 --- /dev/null +++ b/jest.config.edge.js @@ -0,0 +1,19 @@ +require("dotenv").config(); + +module.exports = { + preset: "ts-jest", + projects: [ + { + displayName: "edge-fetch-unit", + testMatch: ["**/*.unit.test.ts"], + testEnvironment: "@edge-runtime/jest-environment", + setupFiles: ["./test/mockFetch.ts", "./test/setupFetch.ts"], + }, + { + displayName: "edge-fetch-int", + testMatch: ["**/*.int.test.ts"], + testEnvironment: "@edge-runtime/jest-environment", + setupFilesAfterEnv: ["./test/setupInt.ts", "./test/setupFetch.ts"], + }, + ], +}; diff --git a/jest.config.js b/jest.config.js index f0d62a0f..171a7a75 100644 --- a/jest.config.js +++ b/jest.config.js @@ -13,12 +13,72 @@ module.exports = { }, projects: [ { - displayName: "dom", + displayName: "dom-axios-unit", + testMatch: ["**/*.unit.test.ts"], testEnvironment: "jsdom", + setupFiles: [ + "./test/mockAxios.ts", + "./test/mockFetch.ts", + "./test/setupAxios.ts", + ], }, { - displayName: "node", + displayName: "dom-fetch-unit", + testMatch: ["**/*.unit.test.ts"], + testEnvironment: "jsdom", + setupFiles: [ + "./test/mockAxios.ts", + "./test/mockFetch.ts", + "./test/setupFetch.ts", + ], + }, + { + displayName: "node-axios-unit", + testMatch: ["**/*.unit.test.ts"], + testEnvironment: "node", + setupFiles: [ + "./test/mockAxios.ts", + "./test/mockFetch.ts", + "./test/setupAxios.ts", + ], + }, + { + displayName: "node-fetch-unit", + testMatch: ["**/*.unit.test.ts"], + testEnvironment: "node", + setupFiles: [ + "./test/mockAxios.ts", + "./test/mockFetch.ts", + "./test/setupFetch.ts", + ], + }, + { + displayName: "dom-axios-int", + testMatch: ["**/*.int.test.ts"], + testEnvironment: "jsdom", + setupFilesAfterEnv: ["./test/setupInt.ts"], + }, + { + displayName: "dom-fetch-int", + testMatch: ["**/*.int.test.ts"], + testEnvironment: "jsdom", + setupFilesAfterEnv: [ + "./test/setupInt.ts", + "./test/polyfillFetch.ts", + "./test/setupFetch.ts", + ], + }, + { + displayName: "node-axios-int", + testMatch: ["**/*.int.test.ts"], + testEnvironment: "node", + setupFilesAfterEnv: ["./test/setupInt.ts", "./test/setupAxios.ts"], + }, + { + displayName: "node-fetch-int", + testMatch: ["**/*.int.test.ts"], testEnvironment: "node", + setupFilesAfterEnv: ["./test/setupInt.ts", "./test/setupFetch.ts"], }, ], }; diff --git a/package-lock.json b/package-lock.json index fe391605..2eb38428 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@xapi/xapi", - "version": "2.2.8", + "version": "3.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@xapi/xapi", - "version": "2.2.8", + "version": "3.0.0", "license": "MIT", "dependencies": { "axios": "^1.6.0" @@ -16,6 +16,7 @@ "@babel/plugin-proposal-optional-chaining": "^7.21.0", "@babel/preset-env": "^7.23.2", "@babel/preset-typescript": "^7.23.2", + "@edge-runtime/jest-environment": "^2.3.10", "@rollup/plugin-babel": "^6.0.4", "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-json": "^6.0.1", @@ -38,7 +39,7 @@ "rollup": "^4.3.0", "ts-jest": "^29.1.1", "typescript": "^5.2.2", - "uuid": "^9.0.1" + "whatwg-fetch": "^3.6.20" }, "funding": { "url": "https://github.com/sponsors/CookieCookson" @@ -1829,6 +1830,176 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "node_modules/@edge-runtime/jest-environment": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@edge-runtime/jest-environment/-/jest-environment-2.3.10.tgz", + "integrity": "sha512-wms2hveQV18DnWkiqzpjzIJ4SoD3+dXzuQ3/GAsklQDCGd4ClkREu5zCy0TFha0mR4I9tqi0DB8nD5Ldd/r1Pg==", + "dev": true, + "dependencies": { + "@edge-runtime/vm": "3.2.0", + "@jest/environment": "29.5.0", + "@jest/fake-timers": "29.5.0", + "jest-mock": "29.5.0", + "jest-util": "29.5.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@edge-runtime/jest-environment/node_modules/@jest/environment": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.5.0.tgz", + "integrity": "sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-mock": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@edge-runtime/jest-environment/node_modules/@jest/fake-timers": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.5.0.tgz", + "integrity": "sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.5.0", + "jest-mock": "^29.5.0", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@edge-runtime/jest-environment/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@edge-runtime/jest-environment/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@edge-runtime/jest-environment/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@edge-runtime/jest-environment/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@edge-runtime/jest-environment/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@edge-runtime/jest-environment/node_modules/jest-mock": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.5.0.tgz", + "integrity": "sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@edge-runtime/jest-environment/node_modules/jest-util": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.5.0.tgz", + "integrity": "sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@edge-runtime/jest-environment/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@edge-runtime/primitives": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@edge-runtime/primitives/-/primitives-4.1.0.tgz", + "integrity": "sha512-Vw0lbJ2lvRUqc7/soqygUX216Xb8T3WBZ987oywz6aJqRxcwSVWwr9e+Nqo2m9bxobA9mdbWNNoRY6S9eko1EQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/@edge-runtime/vm": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@edge-runtime/vm/-/vm-3.2.0.tgz", + "integrity": "sha512-0dEVyRLM/lG4gp1R/Ik5bfPl/1wX00xFwd5KcNH602tzBa09oF7pbTKETEhR1GjZ75K6OJnYFu8II2dyMhONMw==", + "dev": true, + "dependencies": { + "@edge-runtime/primitives": "4.1.0" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -3885,9 +4056,9 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/axios": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", - "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz", + "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -9292,19 +9463,6 @@ "requires-port": "^1.0.0" } }, - "node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "dev": true, - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/v8-to-istanbul": { "version": "9.1.3", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.3.tgz", @@ -9361,6 +9519,12 @@ "node": ">=12" } }, + "node_modules/whatwg-fetch": { + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", + "dev": true + }, "node_modules/whatwg-mimetype": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", diff --git a/package.json b/package.json index dbbba8ab..9ca3d7c0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@xapi/xapi", - "version": "2.2.8", + "version": "3.0.0", "description": "Communicate over xAPI using JavaScript.", "main": "dist/XAPI.cjs.js", "module": "dist/XAPI.esm.js", @@ -16,9 +16,10 @@ "build:types": "tsc --emitDeclarationOnly", "build": "npm run clean && npm run build:types && npm run build:js", "format": "prettier --write '**/*.{js,jsx,json,ts,tsx}'", - "test": "jest --runInBand && npm run test:example:node:require && npm run test:example:node:import", - "test:unit": "jest --testPathPattern=.unit.test.ts", - "test:int": "jest --testPathPattern=.int.test.ts --runInBand", + "test": "jest --runInBand && npm run test:edge && npm run test:example:node:require && npm run test:example:node:import", + "test:edge": "jest --runInBand --config jest.config.edge.js", + "test:unit": "jest --selectProjects dom-axios-unit dom-fetch-unit node-axios-unit node-fetch-unit", + "test:int": "jest --selectProjects dom-axios-int dom-fetch-int node-axios-int node-fetch-int --runInBand", "test:format": "prettier --check .", "test:example:node:require": "node ./example/node/require.js", "test:example:node:import": "node --experimental-modules --es-module-specifier-resolution=node ./example/node/import.mjs", @@ -44,6 +45,7 @@ "@babel/plugin-proposal-optional-chaining": "^7.21.0", "@babel/preset-env": "^7.23.2", "@babel/preset-typescript": "^7.23.2", + "@edge-runtime/jest-environment": "^2.3.10", "@rollup/plugin-babel": "^6.0.4", "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-json": "^6.0.1", @@ -66,7 +68,7 @@ "rollup": "^4.3.0", "ts-jest": "^29.1.1", "typescript": "^5.2.2", - "uuid": "^9.0.1" + "whatwg-fetch": "^3.6.20" }, "dependencies": { "axios": "^1.6.0" diff --git a/src/XAPI.int.test.ts b/src/XAPI.int.test.ts index f799971b..92d9bc49 100644 --- a/src/XAPI.int.test.ts +++ b/src/XAPI.int.test.ts @@ -8,6 +8,7 @@ forEachLRS((_xapi, credential) => { test("can perform basic authentication challenges when no authorization process is required", () => { const noAuthXapi = new XAPI({ endpoint: endpoint, + adapter: global.adapter, }); expect(noAuthXapi.getAbout()).resolves.toBeDefined(); }); diff --git a/src/XAPI.ts b/src/XAPI.ts index f0c9b1c0..c7e41a54 100644 --- a/src/XAPI.ts +++ b/src/XAPI.ts @@ -1,9 +1,3 @@ -import axios, { - AxiosRequestConfig, - AxiosPromise, - AxiosStatic, - RawAxiosRequestHeaders, -} from "axios"; import { AttachmentUsages, Resources, Verbs, Versions } from "./constants"; import { parseMultiPart } from "./internal/multiPart"; import { formatEndpoint } from "./internal/formatEndpoint"; @@ -91,7 +85,12 @@ import { SendStatementParams } from "./resources/statement/sendStatement/SendSta import { SendStatementsParams } from "./resources/statement/sendStatements/SendStatementsParams"; import { VoidStatementParams } from "./resources/statement/voidStatement/VoidStatementParams"; import { VoidStatementsParams } from "./resources/statement/voidStatements/VoidStatementsParams"; - +import { + AdapterFunction, + AdapterPromise, + AdapterRequest, + resolveAdapterFunction, +} from "./adapters"; export * from "./helpers/getTinCanLaunchData/TinCanLaunchData"; export * from "./helpers/getXAPILaunchData/XAPILaunchData"; export * from "./resources/about/About"; @@ -146,6 +145,7 @@ class XAPI { protected endpoint: string; private headers: { [key: string]: string }; + private adapter: AdapterFunction; public constructor(params: XAPIConfig) { const version: Versions = params.version || "1.0.3"; @@ -156,18 +156,15 @@ class XAPI { // No Authorization Process and Requirements - https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Communication.md#no-authorization-process-and-requirements Authorization: params.auth ? params.auth : toBasicAuth("", ""), }; - } - - public getAxios(): AxiosStatic { - return axios; + this.adapter = resolveAdapterFunction(params.adapter); } protected requestResource(params: { resource: Resources; queryParams?: RequestParams; - requestConfig?: AxiosRequestConfig | undefined; + requestConfig?: AdapterRequest | undefined; requestOptions?: GetParamsBase; - }): AxiosPromise { + }): AdapterPromise { const extendedQueryParams = Object.assign({}, params.queryParams); if (params.requestOptions?.useCacheBuster) { extendedQueryParams["cachebuster"] = new Date().getTime().toString(); @@ -178,29 +175,27 @@ class XAPI { protected requestURL( url: string, - requestConfig?: AxiosRequestConfig | undefined - ): AxiosPromise { - return axios - .request({ - method: requestConfig?.method || "GET", - url: url, - headers: { - ...this.headers, - ...(requestConfig?.headers as RawAxiosRequestHeaders), - }, - data: requestConfig?.data, - }) - .then((response) => { - const contentType = response.headers["content-type"]; - if ( - !!response.data && - contentType && - contentType.indexOf("multipart/mixed") !== -1 - ) { - response.data = parseMultiPart(response.data); - } - return response; - }); + requestConfig?: AdapterRequest | undefined + ): AdapterPromise { + return this.adapter({ + url, + method: requestConfig?.method || "GET", + headers: { + ...this.headers, + ...requestConfig?.headers, + }, + data: requestConfig?.data, + }).then((response) => { + const contentType = response.headers["content-type"]; + if ( + !!response.data && + contentType && + contentType.indexOf("multipart/mixed") !== -1 + ) { + response.data = parseMultiPart(response.data); + } + return response; + }); } private generateURL(resource: Resources, params: RequestParams): string { @@ -219,65 +214,67 @@ class XAPI { } interface XAPI { - getAbout(params?: GetAboutParams): AxiosPromise; - getActivity(params: GetActivityParams): AxiosPromise; - getAgent(params: GetAgentParams): AxiosPromise; + getAbout(params?: GetAboutParams): AdapterPromise; + getActivity(params: GetActivityParams): AdapterPromise; + getAgent(params: GetAgentParams): AdapterPromise; createActivityProfile( params: CreateActivityProfileParams - ): AxiosPromise; - setActivityProfile(params: SetActivityProfileParams): AxiosPromise; + ): AdapterPromise; + setActivityProfile(params: SetActivityProfileParams): AdapterPromise; getActivityProfiles( params: GetActivityProfilesParams - ): AxiosPromise; - getActivityProfile(params: GetActivityProfileParams): AxiosPromise; + ): AdapterPromise; + getActivityProfile( + params: GetActivityProfileParams + ): AdapterPromise; deleteActivityProfile( params: DeleteActivityProfileParams - ): AxiosPromise; - createAgentProfile(params: CreateAgentProfileParams): AxiosPromise; - setAgentProfile(params: SetAgentProfileParams): AxiosPromise; - getAgentProfiles(params: GetAgentProfilesParams): AxiosPromise; - getAgentProfile(params: GetAgentProfileParams): AxiosPromise; - deleteAgentProfile(params: DeleteAgentProfileParams): AxiosPromise; - createState(params: CreateStateParams): AxiosPromise; - setState(params: SetStateParams): AxiosPromise; - getStates(params: GetStatesParams): AxiosPromise; - getState(params: GetStateParams): AxiosPromise; - deleteState(params: DeleteStateParams): AxiosPromise; - deleteStates(params: DeleteStatesParams): AxiosPromise; + ): AdapterPromise; + createAgentProfile(params: CreateAgentProfileParams): AdapterPromise; + setAgentProfile(params: SetAgentProfileParams): AdapterPromise; + getAgentProfiles(params: GetAgentProfilesParams): AdapterPromise; + getAgentProfile(params: GetAgentProfileParams): AdapterPromise; + deleteAgentProfile(params: DeleteAgentProfileParams): AdapterPromise; + createState(params: CreateStateParams): AdapterPromise; + setState(params: SetStateParams): AdapterPromise; + getStates(params: GetStatesParams): AdapterPromise; + getState(params: GetStateParams): AdapterPromise; + deleteState(params: DeleteStateParams): AdapterPromise; + deleteStates(params: DeleteStatesParams): AdapterPromise; getStatement( params: GetStatementParamsWithAttachments - ): AxiosPromise; + ): AdapterPromise; getStatement( params: GetStatementParamsWithoutAttachments - ): AxiosPromise; + ): AdapterPromise; getStatement( params: GetStatementParams - ): AxiosPromise; + ): AdapterPromise; getVoidedStatement( params: GetVoidedStatementParamsWithAttachments - ): AxiosPromise; + ): AdapterPromise; getVoidedStatement( params: GetVoidedStatementParamsWithoutAttachments - ): AxiosPromise; + ): AdapterPromise; getVoidedStatement( params: GetVoidedStatementParams - ): AxiosPromise; + ): AdapterPromise; getStatements( params: GetStatementsParamsWithAttachments - ): AxiosPromise; + ): AdapterPromise; getStatements( params: GetStatementsParamsWithoutAttachments - ): AxiosPromise; + ): AdapterPromise; getStatements( params: GetStatementsParams - ): AxiosPromise; + ): AdapterPromise; getMoreStatements( params: GetMoreStatementsParams - ): AxiosPromise; - sendStatement(params: SendStatementParams): AxiosPromise; - sendStatements(params: SendStatementsParams): AxiosPromise; - voidStatement(params: VoidStatementParams): AxiosPromise; - voidStatements(params: VoidStatementsParams): AxiosPromise; + ): AdapterPromise; + sendStatement(params: SendStatementParams): AdapterPromise; + sendStatements(params: SendStatementsParams): AdapterPromise; + voidStatement(params: VoidStatementParams): AdapterPromise; + voidStatements(params: VoidStatementsParams): AdapterPromise; } XAPI.prototype.getAbout = getAbout; diff --git a/src/XAPI.unit.test.ts b/src/XAPI.unit.test.ts index 40fa2d57..99878229 100644 --- a/src/XAPI.unit.test.ts +++ b/src/XAPI.unit.test.ts @@ -1,14 +1,12 @@ import { testEndpoint } from "../test/constants"; import XAPI from "./XAPI"; -import axios from "axios"; import { toBasicAuth } from "./helpers/toBasicAuth/toBasicAuth"; import { Versions } from "./constants"; -jest.mock("axios"); - describe("xapi constructor", () => { beforeEach(() => { - (axios as jest.MockedFunction).request.mockResolvedValueOnce({ + global.adapterFn.mockClear(); + global.adapterFn.mockResolvedValueOnce({ headers: { "content-type": "application/json", }, @@ -18,9 +16,10 @@ describe("xapi constructor", () => { test("can be constructed with an endpoint", () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); xapi.getAbout(); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ headers: expect.objectContaining({ Authorization: toBasicAuth("", ""), @@ -33,9 +32,10 @@ describe("xapi constructor", () => { const xapi = new XAPI({ endpoint: testEndpoint, auth: "test", + adapter: global.adapter, }); xapi.getAbout(); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ headers: expect.objectContaining({ Authorization: "test", @@ -48,9 +48,10 @@ describe("xapi constructor", () => { const xapi = new XAPI({ endpoint: testEndpoint, version: "1.0.0", + adapter: global.adapter, }); xapi.getAbout(); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ headers: expect.objectContaining({ "X-Experience-API-Version": "1.0.0" as Versions, @@ -58,13 +59,4 @@ describe("xapi constructor", () => { }) ); }); - - test("can return the internal axios instance", () => { - const xapi = new XAPI({ - endpoint: testEndpoint, - version: "1.0.0", - }); - const axiosStatic = xapi.getAxios(); - expect(axiosStatic).toEqual(axios); - }); }); diff --git a/src/XAPIConfig.ts b/src/XAPIConfig.ts index baea6be2..ce7f4c86 100644 --- a/src/XAPIConfig.ts +++ b/src/XAPIConfig.ts @@ -1,7 +1,9 @@ +import { Adapter } from "./adapters"; import { Versions } from "./constants"; export interface XAPIConfig { endpoint: string; auth?: string; version?: Versions; + adapter?: Adapter; } diff --git a/src/adapters/axiosAdapter.ts b/src/adapters/axiosAdapter.ts new file mode 100644 index 00000000..fb394a74 --- /dev/null +++ b/src/adapters/axiosAdapter.ts @@ -0,0 +1,19 @@ +import axios from "axios"; +import { AdapterFunction } from "."; + +const axiosAdapter: AdapterFunction = ({ url, method, data, headers }) => { + return axios + .request({ + url, + method, + data, + headers, + }) + .then(({ data, headers, status }) => ({ + data, + headers, + status, + })); +}; + +export default axiosAdapter; diff --git a/src/adapters/axiosAdapter.unit.test.ts b/src/adapters/axiosAdapter.unit.test.ts new file mode 100644 index 00000000..6beb2208 --- /dev/null +++ b/src/adapters/axiosAdapter.unit.test.ts @@ -0,0 +1,27 @@ +import axios from "axios"; +import axiosAdapter from "./axiosAdapter"; +import { isEdgeRuntime, testIf } from "../../test/jestUtils"; + +describe("axiosAdapter", () => { + testIf(!isEdgeRuntime())("makes an axios network request", async () => { + axiosAdapter({ + url: "https://www.example.com", + method: "POST", + data: "foo", + headers: { + Authorization: "Basic ABCDEFG", + }, + }); + + expect(axios.request).toHaveBeenCalledWith( + expect.objectContaining({ + url: "https://www.example.com", + method: "POST", + data: "foo", + headers: { + Authorization: "Basic ABCDEFG", + }, + }) + ); + }); +}); diff --git a/src/adapters/fetchAdapter.ts b/src/adapters/fetchAdapter.ts new file mode 100644 index 00000000..44d3f35b --- /dev/null +++ b/src/adapters/fetchAdapter.ts @@ -0,0 +1,35 @@ +import { AdapterFunction } from "."; + +const fetchAdapter: AdapterFunction = async ({ + url, + method, + data, + headers, +}) => { + let body: any = data; + const contentType: string | undefined = headers["Content-Type"]; + if (contentType === "application/json") { + body = JSON.stringify(body); + } + const response = await fetch(url, { + method, + body, + headers, + }); + if (!response.ok) { + const err = await response.text(); + throw new Error(err); + } + let text: string = await response.text(); + try { + text = JSON.parse(text); + // eslint-disable-next-line no-empty + } catch {} + return { + data: text as any, + headers: Object.fromEntries(response.headers.entries()), + status: response.status, + }; +}; + +export default fetchAdapter; diff --git a/src/adapters/fetchAdapter.unit.test.ts b/src/adapters/fetchAdapter.unit.test.ts new file mode 100644 index 00000000..97609ab6 --- /dev/null +++ b/src/adapters/fetchAdapter.unit.test.ts @@ -0,0 +1,90 @@ +import fetchAdapter from "./fetchAdapter"; + +describe("fetchAdapter", () => { + it("makes a fetch network request", async () => { + fetchAdapter({ + url: "https://www.example.com", + method: "POST", + data: "foo", + headers: { + Authorization: "Basic ABCDEFG", + }, + }); + + expect(fetch).toHaveBeenCalledWith( + "https://www.example.com", + expect.objectContaining({ + method: "POST", + body: "foo", + headers: { + Authorization: "Basic ABCDEFG", + }, + }) + ); + }); + + it("stringifies JSON data", async () => { + fetchAdapter({ + url: "https://www.example.com", + method: "POST", + data: { foo: true }, + headers: { + "Content-Type": "application/json", + }, + }); + + expect(fetch).toHaveBeenCalledWith( + "https://www.example.com", + expect.objectContaining({ + method: "POST", + body: '{"foo":true}', + headers: { + "Content-Type": "application/json", + }, + }) + ); + }); + + it("Returns a rejected promise with error message if an error is encountered", () => { + (fetch as jest.MockedFn).mockImplementationOnce(() => + Promise.resolve({ + text: () => Promise.resolve("i am error"), + ok: false, + } as Response) + ); + + const result = fetchAdapter({ + url: "https://www.example.com", + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + + expect(result).rejects.toThrow("i am error"); + }); + + it("Parses fetch text as JSON", () => { + (fetch as jest.MockedFn).mockImplementationOnce(() => + Promise.resolve({ + text: () => Promise.resolve('{"foo": true}'), + ok: true, + headers: new Headers(), + } as Response) + ); + + const result = fetchAdapter({ + url: "https://www.example.com", + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + + expect(result).resolves.toEqual( + expect.objectContaining({ + data: { foo: true }, + }) + ); + }); +}); diff --git a/src/adapters/index.ts b/src/adapters/index.ts new file mode 100644 index 00000000..8afa4620 --- /dev/null +++ b/src/adapters/index.ts @@ -0,0 +1,33 @@ +import axiosAdapter from "./axiosAdapter"; +import fetchAdapter from "./fetchAdapter"; +import resolveAdapterFunction from "./resolveAdapterFunction"; + +interface AdapterRequest { + method: "GET" | "POST" | "PUT" | "DELETE" | string; + headers?: Record; + data?: any; +} + +interface AdapterResponse { + data: T; + headers: Record; + status: number; +} + +type AdapterPromise = Promise>; + +type AdapterFunction = ( + params: AdapterRequest & { url: string } +) => AdapterPromise; + +type Adapter = "fetch" | "axios" | AdapterFunction; + +export type { + AdapterRequest, + AdapterResponse, + AdapterPromise, + AdapterFunction, + Adapter, +}; + +export { axiosAdapter, fetchAdapter, resolveAdapterFunction }; diff --git a/src/adapters/resolveAdapterFunction.ts b/src/adapters/resolveAdapterFunction.ts new file mode 100644 index 00000000..aa73b570 --- /dev/null +++ b/src/adapters/resolveAdapterFunction.ts @@ -0,0 +1,19 @@ +import { Adapter, AdapterFunction, axiosAdapter, fetchAdapter } from "."; + +const resolveAdapterFunction = (adapter?: Adapter): AdapterFunction => { + if (typeof adapter === "function") { + return adapter; + } else { + switch (adapter) { + case "fetch": { + return fetchAdapter; + } + case "axios": + default: { + return axiosAdapter; + } + } + } +}; + +export default resolveAdapterFunction; diff --git a/src/adapters/resolveAdapterFunction.unit.test.ts b/src/adapters/resolveAdapterFunction.unit.test.ts new file mode 100644 index 00000000..f918cadf --- /dev/null +++ b/src/adapters/resolveAdapterFunction.unit.test.ts @@ -0,0 +1,36 @@ +import { AdapterFunction } from "."; +import axiosAdapter from "./axiosAdapter"; +import fetchAdapter from "./fetchAdapter"; +import resolveAdapterFunction from "./resolveAdapterFunction"; + +describe("resolveAdapterFunction", () => { + it("Returns axios by default", () => { + const result = resolveAdapterFunction(); + + expect(result).toBe(axiosAdapter); + }); + + it("Returns axios if provided as parameter", () => { + const result = resolveAdapterFunction("axios"); + + expect(result).toBe(axiosAdapter); + }); + + it("Returns fetch if provided as parameter", () => { + const result = resolveAdapterFunction("fetch"); + + expect(result).toBe(fetchAdapter); + }); + + it("Returns custom if function provided as parameter", () => { + const customAdapter: AdapterFunction = () => + Promise.resolve({ + data: {} as any, + headers: {}, + status: 0, + }); + const result = resolveAdapterFunction(customAdapter); + + expect(result).toBe(customAdapter); + }); +}); diff --git a/src/helpers/getXAPILaunchData/getXAPILaunchData.ts b/src/helpers/getXAPILaunchData/getXAPILaunchData.ts index e1c73e13..097fda40 100644 --- a/src/helpers/getXAPILaunchData/getXAPILaunchData.ts +++ b/src/helpers/getXAPILaunchData/getXAPILaunchData.ts @@ -1,32 +1,37 @@ +import { + Adapter, + AdapterPromise, + resolveAdapterFunction, +} from "../../adapters"; import { getSearchQueryParamsAsObject } from "../getSearchQueryParamsAsObject/getSearchQueryParamsAsObject"; -import axios from "axios"; import { XAPILaunchData } from "./XAPILaunchData"; import { XAPILaunchParameters } from "./XAPILaunchParameters"; -export function getXAPILaunchData(): Promise { +export function getXAPILaunchData(params?: { + adapter?: Adapter; +}): AdapterPromise { if (typeof location === "undefined") return Promise.reject( new Error("Environment does not support location.search") ); - const params: XAPILaunchParameters = getSearchQueryParamsAsObject( + const launchParams: XAPILaunchParameters = getSearchQueryParamsAsObject( location.search ); - if (!params.xAPILaunchService) { + if (!launchParams.xAPILaunchService) { return Promise.reject( new Error("xAPILaunchService parameter not found in URL.") ); } - const launchURL: URL = new URL(params.xAPILaunchService); - launchURL.pathname += `launch/${params.xAPILaunchKey}`; - return axios - .request({ - method: "POST", - url: launchURL.toString(), - }) - .then((response) => { - return response.data; - }); + const launchURL: URL = new URL(launchParams.xAPILaunchService); + launchURL.pathname += `launch/${launchParams.xAPILaunchKey}`; + const adapter = resolveAdapterFunction(params.adapter); + return adapter({ + method: "POST", + url: launchURL.toString(), + }).then((response) => { + return response.data; + }); } diff --git a/src/helpers/getXAPILaunchData/getXAPILaunchData.unit.test.ts b/src/helpers/getXAPILaunchData/getXAPILaunchData.unit.test.ts index f4b75fb1..9692e8bf 100644 --- a/src/helpers/getXAPILaunchData/getXAPILaunchData.unit.test.ts +++ b/src/helpers/getXAPILaunchData/getXAPILaunchData.unit.test.ts @@ -1,10 +1,7 @@ import { getXAPILaunchData } from "./getXAPILaunchData"; import { testIf, isNode } from "../../../test/jestUtils"; -import axios from "axios"; import { XAPILaunchParameters } from "./XAPILaunchParameters"; -jest.mock("axios"); - testIf(isNode())("return error in node environment", () => { return getXAPILaunchData().catch((error: Error) => { return expect(error.message).toBe( @@ -42,15 +39,16 @@ testIf(!isNode())("can request launch data from url", async () => { }, }); - (axios as jest.MockedFunction).request.mockResolvedValueOnce({ + global.adapterFn.mockClear(); + global.adapterFn.mockResolvedValueOnce({ headers: { "content-type": "application/json", }, }); const launchURL: URL = new URL(params.xAPILaunchService); launchURL.pathname += `launch/${params.xAPILaunchKey}`; - await getXAPILaunchData(); - expect(axios.request).toHaveBeenCalledWith( + await getXAPILaunchData({ adapter: global.adapter }); + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "POST", url: launchURL.toString(), diff --git a/src/helpers/toBasicAuth/toBasicAuth.ts b/src/helpers/toBasicAuth/toBasicAuth.ts index 77849062..4c40e492 100644 --- a/src/helpers/toBasicAuth/toBasicAuth.ts +++ b/src/helpers/toBasicAuth/toBasicAuth.ts @@ -1,6 +1,6 @@ export function toBasicAuth(username: string, password: string): string { const credentials = `${username}:${password}`; - if (typeof window !== "undefined" && window.btoa) { + if (typeof btoa === "function") { return `Basic ${btoa(credentials)}`; } else if (typeof Buffer !== "undefined") { return `Basic ${Buffer.from(credentials, "binary").toString("base64")}`; diff --git a/src/helpers/toBasicAuth/toBasicAuth.unit.test.ts b/src/helpers/toBasicAuth/toBasicAuth.unit.test.ts index 81e2e3d2..4a3293a9 100644 --- a/src/helpers/toBasicAuth/toBasicAuth.unit.test.ts +++ b/src/helpers/toBasicAuth/toBasicAuth.unit.test.ts @@ -23,8 +23,13 @@ test("converts username and password into Basic Auth header", () => { }); test("throws error if environment not supported", () => { - if (typeof window !== "undefined") window.btoa = undefined; + // @ts-expect-error Overriding global/window btoa + // eslint-disable-next-line no-global-assign + if (typeof btoa === "function") btoa = undefined; + // @ts-expect-error Overriding global/window Buffer // eslint-disable-next-line no-global-assign if (Buffer) Buffer = undefined; - expect(() => toBasicAuth("", "")).toThrowError(); + expect(() => toBasicAuth("", "")).toThrow( + new Error("Environment does not support base64 conversion.") + ); }); diff --git a/src/resources/about/getAbout/getAbout.ts b/src/resources/about/getAbout/getAbout.ts index 7fcbd49e..1b7f1f03 100644 --- a/src/resources/about/getAbout/getAbout.ts +++ b/src/resources/about/getAbout/getAbout.ts @@ -1,4 +1,4 @@ -import { AxiosPromise } from "axios"; +import { AdapterPromise } from "../../../adapters"; import { Resources } from "../../../constants"; import XAPI from "../../../XAPI"; import { About } from "../About"; @@ -7,7 +7,7 @@ import { GetAboutParams } from "./GetAboutParams"; export function getAbout( this: XAPI, params?: GetAboutParams -): AxiosPromise { +): AdapterPromise { return this.requestResource({ resource: Resources.ABOUT, requestOptions: { diff --git a/src/resources/about/getAbout/getAbout.unit.test.ts b/src/resources/about/getAbout/getAbout.unit.test.ts index 0fc2cd61..2c4d03a8 100644 --- a/src/resources/about/getAbout/getAbout.unit.test.ts +++ b/src/resources/about/getAbout/getAbout.unit.test.ts @@ -1,13 +1,11 @@ import XAPI from "../../../XAPI"; -import axios from "axios"; import { testEndpoint } from "../../../../test/constants"; import { Resources } from "../../../constants"; -jest.mock("axios"); - describe("about resource", () => { beforeEach(() => { - (axios as jest.MockedFunction).request.mockResolvedValueOnce({ + global.adapterFn.mockClear(); + global.adapterFn.mockResolvedValueOnce({ headers: { "content-type": "application/json", }, @@ -17,9 +15,10 @@ describe("about resource", () => { test("can get about", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); await xapi.getAbout(); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "GET", url: `${testEndpoint}${Resources.ABOUT}`, @@ -30,11 +29,12 @@ describe("about resource", () => { test("can get about with cache buster", () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); xapi.getAbout({ useCacheBuster: true, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ url: expect.stringContaining( `${testEndpoint}${Resources.ABOUT}?cachebuster=` diff --git a/src/resources/activities/getActivity/getActivity.ts b/src/resources/activities/getActivity/getActivity.ts index f04bcb03..b4cda49e 100644 --- a/src/resources/activities/getActivity/getActivity.ts +++ b/src/resources/activities/getActivity/getActivity.ts @@ -1,4 +1,4 @@ -import { AxiosPromise } from "axios"; +import { AdapterPromise } from "../../../adapters"; import { Resources } from "../../../constants"; import XAPI from "../../../XAPI"; import { Activity } from "../Activity"; @@ -7,7 +7,7 @@ import { GetActivityParams } from "./GetActivityParams"; export function getActivity( this: XAPI, params: GetActivityParams -): AxiosPromise { +): AdapterPromise { return this.requestResource({ resource: Resources.ACTIVITIES, queryParams: { diff --git a/src/resources/activities/getActivity/getActivity.unit.test.ts b/src/resources/activities/getActivity/getActivity.unit.test.ts index 6a84ba5b..5917686d 100644 --- a/src/resources/activities/getActivity/getActivity.unit.test.ts +++ b/src/resources/activities/getActivity/getActivity.unit.test.ts @@ -1,13 +1,11 @@ import XAPI from "../../../XAPI"; -import axios from "axios"; import { testActivity, testEndpoint } from "../../../../test/constants"; import { Resources } from "../../../constants"; -jest.mock("axios"); - describe("activities resource", () => { beforeEach(() => { - (axios as jest.MockedFunction).request.mockResolvedValueOnce({ + global.adapterFn.mockClear(); + global.adapterFn.mockResolvedValueOnce({ headers: { "content-type": "application/json", }, @@ -17,11 +15,12 @@ describe("activities resource", () => { test("can get activity", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); await xapi.getActivity({ activityId: testActivity.id, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "GET", url: `${testEndpoint}${ @@ -34,12 +33,13 @@ describe("activities resource", () => { test("can get activity with cache buster", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); await xapi.getActivity({ activityId: testActivity.id, useCacheBuster: true, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "GET", url: expect.stringContaining( diff --git a/src/resources/agents/getAgent/getAgent.ts b/src/resources/agents/getAgent/getAgent.ts index 59d5750f..9bb3463e 100644 --- a/src/resources/agents/getAgent/getAgent.ts +++ b/src/resources/agents/getAgent/getAgent.ts @@ -1,4 +1,4 @@ -import { AxiosPromise } from "axios"; +import { AdapterPromise } from "../../../adapters"; import { Resources } from "../../../constants"; import XAPI from "../../../XAPI"; import { Person } from "../Person"; @@ -7,7 +7,7 @@ import { GetAgentParams } from "./GetAgentParams"; export function getAgent( this: XAPI, params: GetAgentParams -): AxiosPromise { +): AdapterPromise { return this.requestResource({ resource: Resources.AGENTS, queryParams: { diff --git a/src/resources/agents/getAgent/getAgent.unit.test.ts b/src/resources/agents/getAgent/getAgent.unit.test.ts index 71aca99c..12e40391 100644 --- a/src/resources/agents/getAgent/getAgent.unit.test.ts +++ b/src/resources/agents/getAgent/getAgent.unit.test.ts @@ -1,14 +1,12 @@ import XAPI from "../../../XAPI"; -import axios from "axios"; import { testAgent, testEndpoint } from "../../../../test/constants"; import { Resources } from "../../../constants"; -jest.mock("axios"); - describe("agent resource", () => { describe("get agent", () => { beforeEach(() => { - (axios as jest.MockedFunction).request.mockResolvedValueOnce({ + global.adapterFn.mockClear(); + global.adapterFn.mockResolvedValueOnce({ headers: { "content-type": "application/json", }, @@ -18,11 +16,12 @@ describe("agent resource", () => { test("can get person by agent", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); await xapi.getAgent({ agent: testAgent, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "GET", url: `${testEndpoint}${Resources.AGENTS}?agent=${encodeURIComponent( @@ -35,12 +34,13 @@ describe("agent resource", () => { test("can get person by agent with cache buster", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); await xapi.getAgent({ agent: testAgent, useCacheBuster: true, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "GET", url: expect.stringContaining( diff --git a/src/resources/document/activityProfile/createActivityProfile/createActivityProfile.int.test.ts b/src/resources/document/activityProfile/createActivityProfile/createActivityProfile.int.test.ts index 4150f029..37df172d 100644 --- a/src/resources/document/activityProfile/createActivityProfile/createActivityProfile.int.test.ts +++ b/src/resources/document/activityProfile/createActivityProfile/createActivityProfile.int.test.ts @@ -1,4 +1,4 @@ -import { v4 as uuidv4 } from "uuid"; +import crypto from "crypto"; import { testActivity, testProfileId, @@ -21,7 +21,7 @@ forEachLRS((xapi) => { }); test("can add to an activity profile using an etag", () => { - const profileId = uuidv4(); + const profileId = crypto.randomUUID(); return xapi .createActivityProfile({ activityId: testActivity.id, diff --git a/src/resources/document/activityProfile/createActivityProfile/createActivityProfile.ts b/src/resources/document/activityProfile/createActivityProfile/createActivityProfile.ts index 74c2b679..a48ea150 100644 --- a/src/resources/document/activityProfile/createActivityProfile/createActivityProfile.ts +++ b/src/resources/document/activityProfile/createActivityProfile/createActivityProfile.ts @@ -1,4 +1,4 @@ -import { AxiosPromise } from "axios"; +import { AdapterPromise } from "../../../../adapters"; import { Resources } from "../../../../constants"; import XAPI from "../../../../XAPI"; import { CreateActivityProfileParams } from "./CreateActivityProfileParams"; @@ -6,7 +6,7 @@ import { CreateActivityProfileParams } from "./CreateActivityProfileParams"; export function createActivityProfile( this: XAPI, params: CreateActivityProfileParams -): AxiosPromise { +): AdapterPromise { const headers = {}; if (params.etag) headers[params.matchHeader] = params.etag; return this.requestResource({ diff --git a/src/resources/document/activityProfile/createActivityProfile/createActivityProfile.unit.test.ts b/src/resources/document/activityProfile/createActivityProfile/createActivityProfile.unit.test.ts index 57ecf98f..3db1f635 100644 --- a/src/resources/document/activityProfile/createActivityProfile/createActivityProfile.unit.test.ts +++ b/src/resources/document/activityProfile/createActivityProfile/createActivityProfile.unit.test.ts @@ -1,5 +1,4 @@ import XAPI from "../../../../XAPI"; -import axios from "axios"; import { testActivity, testDocument, @@ -8,11 +7,10 @@ import { } from "../../../../../test/constants"; import { Resources } from "../../../../constants"; -jest.mock("axios"); - describe("activity profile resource", () => { beforeEach(() => { - (axios as jest.MockedFunction).request.mockResolvedValueOnce({ + global.adapterFn.mockClear(); + global.adapterFn.mockResolvedValueOnce({ headers: { "content-type": "application/json", }, @@ -22,13 +20,14 @@ describe("activity profile resource", () => { test("can create activity profile", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); await xapi.createActivityProfile({ activityId: testActivity.id, profileId: testProfileId, profile: testDocument, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "POST", url: `${testEndpoint}${ @@ -44,6 +43,7 @@ describe("activity profile resource", () => { test("can create activity profile with etag and match header", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); const testEtag = "my-etag"; const testMatchHeader = "If-Match"; @@ -54,7 +54,7 @@ describe("activity profile resource", () => { etag: testEtag, matchHeader: testMatchHeader, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "POST", headers: expect.objectContaining({ diff --git a/src/resources/document/activityProfile/deleteActivityProfile/deleteActivityProfile.int.test.ts b/src/resources/document/activityProfile/deleteActivityProfile/deleteActivityProfile.int.test.ts index 62707466..bb1b486a 100644 --- a/src/resources/document/activityProfile/deleteActivityProfile/deleteActivityProfile.int.test.ts +++ b/src/resources/document/activityProfile/deleteActivityProfile/deleteActivityProfile.int.test.ts @@ -1,4 +1,4 @@ -import { v4 as uuidv4 } from "uuid"; +import crypto from "crypto"; import { testActivity, testDocument, @@ -20,7 +20,7 @@ forEachLRS((xapi) => { }); test("can delete an activity profile with an etag", () => { - const profileId = uuidv4(); + const profileId = crypto.randomUUID(); return xapi .createActivityProfile({ activityId: testActivity.id, diff --git a/src/resources/document/activityProfile/deleteActivityProfile/deleteActivityProfile.ts b/src/resources/document/activityProfile/deleteActivityProfile/deleteActivityProfile.ts index 43a0fc54..7b41751e 100644 --- a/src/resources/document/activityProfile/deleteActivityProfile/deleteActivityProfile.ts +++ b/src/resources/document/activityProfile/deleteActivityProfile/deleteActivityProfile.ts @@ -1,4 +1,4 @@ -import { AxiosPromise } from "axios"; +import { AdapterPromise } from "../../../../adapters"; import { Resources } from "../../../../constants"; import XAPI from "../../../../XAPI"; import { DeleteActivityProfileParams } from "./DeleteActivityProfileParams"; @@ -6,7 +6,7 @@ import { DeleteActivityProfileParams } from "./DeleteActivityProfileParams"; export function deleteActivityProfile( this: XAPI, params: DeleteActivityProfileParams -): AxiosPromise { +): AdapterPromise { const headers = {}; if (params.etag) headers["If-Match"] = params.etag; return this.requestResource({ diff --git a/src/resources/document/activityProfile/deleteActivityProfile/deleteActivityProfile.unit.test.ts b/src/resources/document/activityProfile/deleteActivityProfile/deleteActivityProfile.unit.test.ts index c68813d3..aab11c4b 100644 --- a/src/resources/document/activityProfile/deleteActivityProfile/deleteActivityProfile.unit.test.ts +++ b/src/resources/document/activityProfile/deleteActivityProfile/deleteActivityProfile.unit.test.ts @@ -1,5 +1,4 @@ import XAPI from "../../../../XAPI"; -import axios from "axios"; import { testActivity, testEndpoint, @@ -7,11 +6,10 @@ import { } from "../../../../../test/constants"; import { Resources } from "../../../../constants"; -jest.mock("axios"); - describe("activity profile resource", () => { beforeEach(() => { - (axios as jest.MockedFunction).request.mockResolvedValueOnce({ + global.adapterFn.mockClear(); + global.adapterFn.mockResolvedValueOnce({ headers: { "content-type": "application/json", }, @@ -21,12 +19,13 @@ describe("activity profile resource", () => { test("can delete an activity profile", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); await xapi.deleteActivityProfile({ activityId: testActivity.id, profileId: testProfileId, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "DELETE", url: `${testEndpoint}${ @@ -41,6 +40,7 @@ describe("activity profile resource", () => { test("can delete an activity profile with etag and match header", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); const testEtag = "my-etag"; await xapi.deleteActivityProfile({ @@ -48,7 +48,7 @@ describe("activity profile resource", () => { profileId: testProfileId, etag: testEtag, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "DELETE", url: `${testEndpoint}${ diff --git a/src/resources/document/activityProfile/getActivityProfile/getActivityProfile.ts b/src/resources/document/activityProfile/getActivityProfile/getActivityProfile.ts index 52c07c10..5991bdb8 100644 --- a/src/resources/document/activityProfile/getActivityProfile/getActivityProfile.ts +++ b/src/resources/document/activityProfile/getActivityProfile/getActivityProfile.ts @@ -1,4 +1,4 @@ -import { AxiosPromise } from "axios"; +import { AdapterPromise } from "../../../../adapters"; import { Resources } from "../../../../constants"; import XAPI from "../../../../XAPI"; import { GetActivityProfileParams } from "./GetActivityProfileParams"; @@ -7,7 +7,7 @@ import { Document } from "../../Document"; export function getActivityProfile( this: XAPI, params: GetActivityProfileParams -): AxiosPromise { +): AdapterPromise { return this.requestResource({ resource: Resources.ACTIVITY_PROFILE, queryParams: { diff --git a/src/resources/document/activityProfile/getActivityProfile/getActivityProfile.unit.test.ts b/src/resources/document/activityProfile/getActivityProfile/getActivityProfile.unit.test.ts index da34de12..67423a60 100644 --- a/src/resources/document/activityProfile/getActivityProfile/getActivityProfile.unit.test.ts +++ b/src/resources/document/activityProfile/getActivityProfile/getActivityProfile.unit.test.ts @@ -1,5 +1,4 @@ import XAPI from "../../../../XAPI"; -import axios from "axios"; import { testActivity, testEndpoint, @@ -7,11 +6,10 @@ import { } from "../../../../../test/constants"; import { Resources } from "../../../../constants"; -jest.mock("axios"); - describe("activity profile resource", () => { beforeEach(() => { - (axios as jest.MockedFunction).request.mockResolvedValueOnce({ + global.adapterFn.mockClear(); + global.adapterFn.mockResolvedValueOnce({ headers: { "content-type": "application/json", }, @@ -21,12 +19,13 @@ describe("activity profile resource", () => { test("can get an activity profile", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); await xapi.getActivityProfile({ activityId: testActivity.id, profileId: testProfileId, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "GET", url: `${testEndpoint}${ @@ -41,13 +40,14 @@ describe("activity profile resource", () => { test("can get an activity profile with cache buster", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); await xapi.getActivityProfile({ activityId: testActivity.id, profileId: testProfileId, useCacheBuster: true, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "GET", url: expect.stringContaining( diff --git a/src/resources/document/activityProfile/getActivityProfiles/getActivityProfiles.ts b/src/resources/document/activityProfile/getActivityProfiles/getActivityProfiles.ts index ada2173b..f28676f2 100644 --- a/src/resources/document/activityProfile/getActivityProfiles/getActivityProfiles.ts +++ b/src/resources/document/activityProfile/getActivityProfiles/getActivityProfiles.ts @@ -1,4 +1,4 @@ -import { AxiosPromise } from "axios"; +import { AdapterPromise } from "../../../../adapters"; import { Resources } from "../../../../constants"; import XAPI from "../../../../XAPI"; import { GetActivityProfilesParams } from "./GetActivityProfilesParams"; @@ -6,7 +6,7 @@ import { GetActivityProfilesParams } from "./GetActivityProfilesParams"; export function getActivityProfiles( this: XAPI, params: GetActivityProfilesParams -): AxiosPromise { +): AdapterPromise { return this.requestResource({ resource: Resources.ACTIVITY_PROFILE, queryParams: { diff --git a/src/resources/document/activityProfile/getActivityProfiles/getActivityProfiles.unit.test.ts b/src/resources/document/activityProfile/getActivityProfiles/getActivityProfiles.unit.test.ts index 936433bf..543a8e73 100644 --- a/src/resources/document/activityProfile/getActivityProfiles/getActivityProfiles.unit.test.ts +++ b/src/resources/document/activityProfile/getActivityProfiles/getActivityProfiles.unit.test.ts @@ -1,13 +1,11 @@ import XAPI from "../../../../XAPI"; -import axios from "axios"; import { testActivity, testEndpoint } from "../../../../../test/constants"; import { Resources } from "../../../../constants"; -jest.mock("axios"); - describe("activity profile resource", () => { beforeEach(() => { - (axios as jest.MockedFunction).request.mockResolvedValueOnce({ + global.adapterFn.mockClear(); + global.adapterFn.mockResolvedValueOnce({ headers: { "content-type": "application/json", }, @@ -17,11 +15,12 @@ describe("activity profile resource", () => { test("can get all activity profiles", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); await xapi.getActivityProfiles({ activityId: testActivity.id, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "GET", url: `${testEndpoint}${ @@ -34,6 +33,7 @@ describe("activity profile resource", () => { test("can get all activity profiles since a certain date", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); const since = new Date(); since.setDate(since.getDate() - 1); // yesterday @@ -41,7 +41,7 @@ describe("activity profile resource", () => { activityId: testActivity.id, since: since.toISOString(), }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "GET", url: `${testEndpoint}${ @@ -56,12 +56,13 @@ describe("activity profile resource", () => { test("can get all activity profiles with cache buster", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); await xapi.getActivityProfiles({ activityId: testActivity.id, useCacheBuster: true, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "GET", url: expect.stringContaining( diff --git a/src/resources/document/activityProfile/setActivityProfile/setActivityProfile.ts b/src/resources/document/activityProfile/setActivityProfile/setActivityProfile.ts index 7cc572c6..9e9051c3 100644 --- a/src/resources/document/activityProfile/setActivityProfile/setActivityProfile.ts +++ b/src/resources/document/activityProfile/setActivityProfile/setActivityProfile.ts @@ -1,4 +1,4 @@ -import { AxiosPromise } from "axios"; +import { AdapterPromise } from "../../../../adapters"; import { Resources } from "../../../../constants"; import XAPI from "../../../../XAPI"; import { SetActivityProfileParams } from "./SetActivityProfileParams"; @@ -6,7 +6,7 @@ import { SetActivityProfileParams } from "./SetActivityProfileParams"; export function setActivityProfile( this: XAPI, params: SetActivityProfileParams -): AxiosPromise { +): AdapterPromise { const headers = {}; headers[params.matchHeader] = params.etag; if (params.contentType) headers["Content-Type"] = params.contentType; diff --git a/src/resources/document/activityProfile/setActivityProfile/setActivityProfile.unit.test.ts b/src/resources/document/activityProfile/setActivityProfile/setActivityProfile.unit.test.ts index 37a96e12..44a5e686 100644 --- a/src/resources/document/activityProfile/setActivityProfile/setActivityProfile.unit.test.ts +++ b/src/resources/document/activityProfile/setActivityProfile/setActivityProfile.unit.test.ts @@ -1,5 +1,4 @@ import XAPI from "../../../../XAPI"; -import axios from "axios"; import { testActivity, testDocument, @@ -9,11 +8,10 @@ import { } from "../../../../../test/constants"; import { Resources } from "../../../../constants"; -jest.mock("axios"); - describe("activity profile resource", () => { beforeEach(() => { - (axios as jest.MockedFunction).request.mockResolvedValueOnce({ + global.adapterFn.mockClear(); + global.adapterFn.mockResolvedValueOnce({ headers: { "content-type": "application/json", }, @@ -23,6 +21,7 @@ describe("activity profile resource", () => { test("can set activity profile", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); const testEtag = "my-etag"; const testMatchHeader = "If-Match"; @@ -33,7 +32,7 @@ describe("activity profile resource", () => { etag: testEtag, matchHeader: testMatchHeader, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "PUT", headers: expect.objectContaining({ @@ -52,6 +51,7 @@ describe("activity profile resource", () => { test("can set activity profile with text/plain content type", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); const testEtag = "my-etag"; const testMatchHeader = "If-Match"; @@ -64,7 +64,7 @@ describe("activity profile resource", () => { matchHeader: testMatchHeader, contentType: plainTextContentType, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "PUT", headers: expect.objectContaining({ diff --git a/src/resources/document/agentProfile/createAgentProfile/createAgentProfile.ts b/src/resources/document/agentProfile/createAgentProfile/createAgentProfile.ts index aa2186f4..ab9cb40c 100644 --- a/src/resources/document/agentProfile/createAgentProfile/createAgentProfile.ts +++ b/src/resources/document/agentProfile/createAgentProfile/createAgentProfile.ts @@ -1,4 +1,4 @@ -import { AxiosPromise } from "axios"; +import { AdapterPromise } from "../../../../adapters"; import { Resources } from "../../../../constants"; import XAPI from "../../../../XAPI"; import { CreateAgentProfileParams } from "./CreateAgentProfileParams"; @@ -6,7 +6,7 @@ import { CreateAgentProfileParams } from "./CreateAgentProfileParams"; export function createAgentProfile( this: XAPI, params: CreateAgentProfileParams -): AxiosPromise { +): AdapterPromise { const headers = {}; if (params.etag) headers[params.matchHeader] = params.etag; return this.requestResource({ diff --git a/src/resources/document/agentProfile/createAgentProfile/createAgentProfile.unit.test.ts b/src/resources/document/agentProfile/createAgentProfile/createAgentProfile.unit.test.ts index 6eccca8f..209f8e7c 100644 --- a/src/resources/document/agentProfile/createAgentProfile/createAgentProfile.unit.test.ts +++ b/src/resources/document/agentProfile/createAgentProfile/createAgentProfile.unit.test.ts @@ -1,5 +1,4 @@ import XAPI from "../../../../XAPI"; -import axios from "axios"; import { testAgent, testDocument, @@ -8,11 +7,10 @@ import { } from "../../../../../test/constants"; import { Resources } from "../../../../constants"; -jest.mock("axios"); - describe("agent profile resource", () => { beforeEach(() => { - (axios as jest.MockedFunction).request.mockResolvedValueOnce({ + global.adapterFn.mockClear(); + global.adapterFn.mockResolvedValueOnce({ headers: { "content-type": "application/json", }, @@ -22,13 +20,14 @@ describe("agent profile resource", () => { test("can create agent profile", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); await xapi.createAgentProfile({ agent: testAgent, profileId: testProfileId, profile: testDocument, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "POST", url: `${testEndpoint}${ @@ -44,6 +43,7 @@ describe("agent profile resource", () => { test("can create agent profile with an etag", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); const testEtag = "my-etag"; const testMatchHeader = "If-Match"; @@ -54,7 +54,7 @@ describe("agent profile resource", () => { etag: testEtag, matchHeader: testMatchHeader, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "POST", url: `${testEndpoint}${ diff --git a/src/resources/document/agentProfile/deleteAgentProfile/deleteAgentProfile.ts b/src/resources/document/agentProfile/deleteAgentProfile/deleteAgentProfile.ts index d37939c0..0e9383fb 100644 --- a/src/resources/document/agentProfile/deleteAgentProfile/deleteAgentProfile.ts +++ b/src/resources/document/agentProfile/deleteAgentProfile/deleteAgentProfile.ts @@ -1,4 +1,4 @@ -import { AxiosPromise } from "axios"; +import { AdapterPromise } from "../../../../adapters"; import { Resources } from "../../../../constants"; import XAPI from "../../../../XAPI"; import { DeleteAgentProfileParams } from "./DeleteAgentProfileParams"; @@ -6,7 +6,7 @@ import { DeleteAgentProfileParams } from "./DeleteAgentProfileParams"; export function deleteAgentProfile( this: XAPI, params: DeleteAgentProfileParams -): AxiosPromise { +): AdapterPromise { const headers = {}; if (params.etag) headers["If-Match"] = params.etag; return this.requestResource({ diff --git a/src/resources/document/agentProfile/deleteAgentProfile/deleteAgentProfile.unit.test.ts b/src/resources/document/agentProfile/deleteAgentProfile/deleteAgentProfile.unit.test.ts index 827fb7e9..ede1b345 100644 --- a/src/resources/document/agentProfile/deleteAgentProfile/deleteAgentProfile.unit.test.ts +++ b/src/resources/document/agentProfile/deleteAgentProfile/deleteAgentProfile.unit.test.ts @@ -1,5 +1,4 @@ import XAPI from "../../../../XAPI"; -import axios from "axios"; import { testAgent, testEndpoint, @@ -7,11 +6,10 @@ import { } from "../../../../../test/constants"; import { Resources } from "../../../../constants"; -jest.mock("axios"); - describe("agent profile resource", () => { beforeEach(() => { - (axios as jest.MockedFunction).request.mockResolvedValueOnce({ + global.adapterFn.mockClear(); + global.adapterFn.mockResolvedValueOnce({ headers: { "content-type": "application/json", }, @@ -21,12 +19,13 @@ describe("agent profile resource", () => { test("can delete an agent profile", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); await xapi.deleteAgentProfile({ agent: testAgent, profileId: testProfileId, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "DELETE", url: `${testEndpoint}${ @@ -41,6 +40,7 @@ describe("agent profile resource", () => { test("can delete an agent profile with an etag", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); const testEtag = "my-etag"; await xapi.deleteAgentProfile({ @@ -48,7 +48,7 @@ describe("agent profile resource", () => { profileId: testProfileId, etag: testEtag, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "DELETE", url: `${testEndpoint}${ diff --git a/src/resources/document/agentProfile/getAgentProfile/getAgentProfile.ts b/src/resources/document/agentProfile/getAgentProfile/getAgentProfile.ts index aa308864..5bc89aa4 100644 --- a/src/resources/document/agentProfile/getAgentProfile/getAgentProfile.ts +++ b/src/resources/document/agentProfile/getAgentProfile/getAgentProfile.ts @@ -1,4 +1,4 @@ -import { AxiosPromise } from "axios"; +import { AdapterPromise } from "../../../../adapters"; import { Resources } from "../../../../constants"; import XAPI from "../../../../XAPI"; import { Document } from "../../Document"; @@ -7,7 +7,7 @@ import { GetAgentProfileParams } from "./GetAgentProfileParams"; export function getAgentProfile( this: XAPI, params: GetAgentProfileParams -): AxiosPromise { +): AdapterPromise { return this.requestResource({ resource: Resources.AGENT_PROFILE, queryParams: { diff --git a/src/resources/document/agentProfile/getAgentProfile/getAgentProfile.unit.test.ts b/src/resources/document/agentProfile/getAgentProfile/getAgentProfile.unit.test.ts index c90806a5..254cebf4 100644 --- a/src/resources/document/agentProfile/getAgentProfile/getAgentProfile.unit.test.ts +++ b/src/resources/document/agentProfile/getAgentProfile/getAgentProfile.unit.test.ts @@ -1,5 +1,4 @@ import XAPI from "../../../../XAPI"; -import axios from "axios"; import { testAgent, testEndpoint, @@ -7,11 +6,10 @@ import { } from "../../../../../test/constants"; import { Resources } from "../../../../constants"; -jest.mock("axios"); - describe("agent profile resource", () => { beforeEach(() => { - (axios as jest.MockedFunction).request.mockResolvedValueOnce({ + global.adapterFn.mockClear(); + global.adapterFn.mockResolvedValueOnce({ headers: { "content-type": "application/json", }, @@ -21,12 +19,13 @@ describe("agent profile resource", () => { test("can get an agent profile", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); await xapi.getAgentProfile({ agent: testAgent, profileId: testProfileId, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "GET", url: `${testEndpoint}${ @@ -41,13 +40,14 @@ describe("agent profile resource", () => { test("can get an agent profile with cache buster", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); await xapi.getAgentProfile({ agent: testAgent, profileId: testProfileId, useCacheBuster: true, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "GET", url: expect.stringContaining( diff --git a/src/resources/document/agentProfile/getAgentProfiles/getAgentProfiles.ts b/src/resources/document/agentProfile/getAgentProfiles/getAgentProfiles.ts index 15b28c1f..f264f018 100644 --- a/src/resources/document/agentProfile/getAgentProfiles/getAgentProfiles.ts +++ b/src/resources/document/agentProfile/getAgentProfiles/getAgentProfiles.ts @@ -1,4 +1,4 @@ -import { AxiosPromise } from "axios"; +import { AdapterPromise } from "../../../../adapters"; import { Resources } from "../../../../constants"; import XAPI from "../../../../XAPI"; import { GetAgentProfilesParams } from "./GetAgentProfilesParams"; @@ -6,7 +6,7 @@ import { GetAgentProfilesParams } from "./GetAgentProfilesParams"; export function getAgentProfiles( this: XAPI, params: GetAgentProfilesParams -): AxiosPromise { +): AdapterPromise { return this.requestResource({ resource: Resources.AGENT_PROFILE, queryParams: { diff --git a/src/resources/document/agentProfile/getAgentProfiles/getAgentProfiles.unit.test.ts b/src/resources/document/agentProfile/getAgentProfiles/getAgentProfiles.unit.test.ts index 0937937b..5b44f0c5 100644 --- a/src/resources/document/agentProfile/getAgentProfiles/getAgentProfiles.unit.test.ts +++ b/src/resources/document/agentProfile/getAgentProfiles/getAgentProfiles.unit.test.ts @@ -1,13 +1,11 @@ import XAPI from "../../../../XAPI"; -import axios from "axios"; import { testAgent, testEndpoint } from "../../../../../test/constants"; import { Resources } from "../../../../constants"; -jest.mock("axios"); - describe("agent profile resource", () => { beforeEach(() => { - (axios as jest.MockedFunction).request.mockResolvedValueOnce({ + global.adapterFn.mockClear(); + global.adapterFn.mockResolvedValueOnce({ headers: { "content-type": "application/json", }, @@ -17,11 +15,12 @@ describe("agent profile resource", () => { test("can get all agent profiles", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); await xapi.getAgentProfiles({ agent: testAgent, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "GET", url: `${testEndpoint}${ @@ -34,6 +33,7 @@ describe("agent profile resource", () => { test("can get all agent profiles since a certain date", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); const since = new Date(); since.setDate(since.getDate() - 1); // yesterday @@ -41,7 +41,7 @@ describe("agent profile resource", () => { agent: testAgent, since: since.toISOString(), }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "GET", url: `${testEndpoint}${ @@ -56,12 +56,13 @@ describe("agent profile resource", () => { test("can get all agent profiles with cache buster", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); await xapi.getAgentProfiles({ agent: testAgent, useCacheBuster: true, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "GET", url: expect.stringContaining( diff --git a/src/resources/document/agentProfile/setAgentProfile/setAgentProfile.ts b/src/resources/document/agentProfile/setAgentProfile/setAgentProfile.ts index 47420d1c..eb8dc2af 100644 --- a/src/resources/document/agentProfile/setAgentProfile/setAgentProfile.ts +++ b/src/resources/document/agentProfile/setAgentProfile/setAgentProfile.ts @@ -1,4 +1,4 @@ -import { AxiosPromise } from "axios"; +import { AdapterPromise } from "../../../../adapters"; import { Resources } from "../../../../constants"; import XAPI from "../../../../XAPI"; import { SetAgentProfileParams } from "./SetAgentProfileParams"; @@ -6,7 +6,7 @@ import { SetAgentProfileParams } from "./SetAgentProfileParams"; export function setAgentProfile( this: XAPI, params: SetAgentProfileParams -): AxiosPromise { +): AdapterPromise { const headers = {}; headers[params.matchHeader] = params.etag; if (params.contentType) headers["Content-Type"] = params.contentType; diff --git a/src/resources/document/agentProfile/setAgentProfile/setAgentProfile.unit.test.ts b/src/resources/document/agentProfile/setAgentProfile/setAgentProfile.unit.test.ts index 82a230de..f4b8eaf7 100644 --- a/src/resources/document/agentProfile/setAgentProfile/setAgentProfile.unit.test.ts +++ b/src/resources/document/agentProfile/setAgentProfile/setAgentProfile.unit.test.ts @@ -1,5 +1,4 @@ import XAPI from "../../../../XAPI"; -import axios from "axios"; import { testAgent, testDocument, @@ -8,11 +7,10 @@ import { } from "../../../../../test/constants"; import { Resources } from "../../../../constants"; -jest.mock("axios"); - describe("agent profile resource", () => { beforeEach(() => { - (axios as jest.MockedFunction).request.mockResolvedValueOnce({ + global.adapterFn.mockClear(); + global.adapterFn.mockResolvedValueOnce({ headers: { "content-type": "application/json", }, @@ -22,6 +20,7 @@ describe("agent profile resource", () => { test("can set agent profile", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); const testEtag = "my-etag"; const testMatchHeader = "If-Match"; @@ -32,7 +31,7 @@ describe("agent profile resource", () => { etag: testEtag, matchHeader: testMatchHeader, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "PUT", url: `${testEndpoint}${ @@ -51,6 +50,7 @@ describe("agent profile resource", () => { test("can set agent profile with content type", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); const testEtag = "my-etag"; const testMatchHeader = "If-Match"; @@ -63,7 +63,7 @@ describe("agent profile resource", () => { matchHeader: testMatchHeader, contentType: plainTextContentType, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "PUT", url: `${testEndpoint}${ diff --git a/src/resources/document/state/createState/createState.int.test.ts b/src/resources/document/state/createState/createState.int.test.ts index 43d7924b..324610d1 100644 --- a/src/resources/document/state/createState/createState.int.test.ts +++ b/src/resources/document/state/createState/createState.int.test.ts @@ -1,4 +1,4 @@ -import { v4 as uuidv4 } from "uuid"; +import crypto from "crypto"; import { testAgent, testActivity, @@ -30,7 +30,7 @@ forEachLRS((xapi) => { activityId: testActivity.id, stateId: testStateId, state: testDocument, - registration: uuidv4(), + registration: crypto.randomUUID(), }) .then((response) => { return expect(response.data).toBeDefined(); diff --git a/src/resources/document/state/createState/createState.ts b/src/resources/document/state/createState/createState.ts index c0f5bb75..fa4757b8 100644 --- a/src/resources/document/state/createState/createState.ts +++ b/src/resources/document/state/createState/createState.ts @@ -1,4 +1,4 @@ -import { AxiosPromise } from "axios"; +import { AdapterPromise } from "../../../../adapters"; import { Resources } from "../../../../constants"; import XAPI from "../../../../XAPI"; import { CreateStateParams } from "./CreateStateParams"; @@ -6,7 +6,7 @@ import { CreateStateParams } from "./CreateStateParams"; export function createState( this: XAPI, params: CreateStateParams -): AxiosPromise { +): AdapterPromise { const headers = {}; if (params.etag && params.matchHeader) headers[params.matchHeader] = params.etag; diff --git a/src/resources/document/state/createState/createState.unit.test.ts b/src/resources/document/state/createState/createState.unit.test.ts index 6343c243..c92f2fd0 100644 --- a/src/resources/document/state/createState/createState.unit.test.ts +++ b/src/resources/document/state/createState/createState.unit.test.ts @@ -1,5 +1,4 @@ import XAPI from "../../../../XAPI"; -import axios from "axios"; import { testActivity, testAgent, @@ -9,11 +8,10 @@ import { } from "../../../../../test/constants"; import { Resources } from "../../../../constants"; -jest.mock("axios"); - describe("state resource", () => { beforeEach(() => { - (axios as jest.MockedFunction).request.mockResolvedValueOnce({ + global.adapterFn.mockClear(); + global.adapterFn.mockResolvedValueOnce({ headers: { "content-type": "application/json", }, @@ -23,6 +21,7 @@ describe("state resource", () => { test("can create state", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); await xapi.createState({ agent: testAgent, @@ -30,7 +29,7 @@ describe("state resource", () => { stateId: testStateId, state: testDocument, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "POST", url: `${testEndpoint}${Resources.STATE}?agent=${encodeURIComponent( @@ -46,6 +45,7 @@ describe("state resource", () => { test("can create state with registration", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); const testRegistration = "test-registration"; await xapi.createState({ @@ -55,7 +55,7 @@ describe("state resource", () => { state: testDocument, registration: testRegistration, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "POST", url: `${testEndpoint}${Resources.STATE}?agent=${encodeURIComponent( @@ -73,6 +73,7 @@ describe("state resource", () => { test("can create state with etag and match header", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); const testEtag = "my-etag"; const testMatchHeader = "If-Match"; @@ -84,7 +85,7 @@ describe("state resource", () => { etag: testEtag, matchHeader: testMatchHeader, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "POST", headers: expect.objectContaining({ diff --git a/src/resources/document/state/deleteState/deleteState.int.test.ts b/src/resources/document/state/deleteState/deleteState.int.test.ts index 2dce3e80..e42dcab5 100644 --- a/src/resources/document/state/deleteState/deleteState.int.test.ts +++ b/src/resources/document/state/deleteState/deleteState.int.test.ts @@ -1,4 +1,4 @@ -import { v4 as uuidv4 } from "uuid"; +import crypto from "crypto"; import { testAgent, testActivity, @@ -23,7 +23,7 @@ forEachLRS((xapi) => { }); test("can delete a state with a registration", () => { - const registration = uuidv4(); + const registration = crypto.randomUUID(); return xapi .createState({ agent: testAgent, diff --git a/src/resources/document/state/deleteState/deleteState.ts b/src/resources/document/state/deleteState/deleteState.ts index 7d5fd3d1..e6672575 100644 --- a/src/resources/document/state/deleteState/deleteState.ts +++ b/src/resources/document/state/deleteState/deleteState.ts @@ -1,4 +1,4 @@ -import { AxiosPromise } from "axios"; +import { AdapterPromise } from "../../../../adapters"; import { Resources } from "../../../../constants"; import XAPI from "../../../../XAPI"; import { DeleteStateParams } from "./DeleteStateParams"; @@ -6,7 +6,7 @@ import { DeleteStateParams } from "./DeleteStateParams"; export function deleteState( this: XAPI, params: DeleteStateParams -): AxiosPromise { +): AdapterPromise { const headers = {}; if (params.etag) headers["If-Match"] = params.etag; return this.requestResource({ diff --git a/src/resources/document/state/deleteState/deleteState.unit.test.ts b/src/resources/document/state/deleteState/deleteState.unit.test.ts index 581afeb3..79ab5411 100644 --- a/src/resources/document/state/deleteState/deleteState.unit.test.ts +++ b/src/resources/document/state/deleteState/deleteState.unit.test.ts @@ -1,5 +1,4 @@ import XAPI from "../../../../XAPI"; -import axios from "axios"; import { testActivity, testAgent, @@ -8,11 +7,10 @@ import { } from "../../../../../test/constants"; import { Resources } from "../../../../constants"; -jest.mock("axios"); - describe("state resource", () => { beforeEach(() => { - (axios as jest.MockedFunction).request.mockResolvedValueOnce({ + global.adapterFn.mockClear(); + global.adapterFn.mockResolvedValueOnce({ headers: { "content-type": "application/json", }, @@ -22,13 +20,14 @@ describe("state resource", () => { test("can delete a state", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); await xapi.deleteState({ agent: testAgent, activityId: testActivity.id, stateId: testStateId, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "DELETE", url: `${testEndpoint}${Resources.STATE}?agent=${encodeURIComponent( @@ -43,6 +42,7 @@ describe("state resource", () => { test("can delete a state with registration", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); const testRegistration = "test-registration"; await xapi.deleteState({ @@ -51,7 +51,7 @@ describe("state resource", () => { stateId: testStateId, registration: testRegistration, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "DELETE", url: `${testEndpoint}${Resources.STATE}?agent=${encodeURIComponent( @@ -68,6 +68,7 @@ describe("state resource", () => { test("can delete a state with etag", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); const testEtag = "my-etag"; await xapi.deleteState({ @@ -76,7 +77,7 @@ describe("state resource", () => { stateId: testStateId, etag: testEtag, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "DELETE", headers: expect.objectContaining({ diff --git a/src/resources/document/state/deleteStates/deleteStates.int.test.ts b/src/resources/document/state/deleteStates/deleteStates.int.test.ts index 7d3e25e2..e8db11ec 100644 --- a/src/resources/document/state/deleteStates/deleteStates.int.test.ts +++ b/src/resources/document/state/deleteStates/deleteStates.int.test.ts @@ -1,4 +1,4 @@ -import { v4 as uuidv4 } from "uuid"; +import crypto from "crypto"; import { testActivity, testAgent, @@ -22,7 +22,7 @@ forEachLRS((xapi) => { }); test("can delete all states for a registration", () => { - const registration = uuidv4(); + const registration = crypto.randomUUID(); return xapi .createState({ agent: testAgent, diff --git a/src/resources/document/state/deleteStates/deleteStates.ts b/src/resources/document/state/deleteStates/deleteStates.ts index 77e58950..73160cc9 100644 --- a/src/resources/document/state/deleteStates/deleteStates.ts +++ b/src/resources/document/state/deleteStates/deleteStates.ts @@ -1,4 +1,4 @@ -import { AxiosPromise } from "axios"; +import { AdapterPromise } from "../../../../adapters"; import { Resources } from "../../../../constants"; import XAPI from "../../../../XAPI"; import { DeleteStatesParams } from "./DeleteStatesParams"; @@ -6,7 +6,7 @@ import { DeleteStatesParams } from "./DeleteStatesParams"; export function deleteStates( this: XAPI, params: DeleteStatesParams -): AxiosPromise { +): AdapterPromise { const headers = {}; if (params.etag) headers["If-Match"] = params.etag; return this.requestResource({ diff --git a/src/resources/document/state/deleteStates/deleteStates.unit.test.ts b/src/resources/document/state/deleteStates/deleteStates.unit.test.ts index 1011ddc3..ecbbd5fb 100644 --- a/src/resources/document/state/deleteStates/deleteStates.unit.test.ts +++ b/src/resources/document/state/deleteStates/deleteStates.unit.test.ts @@ -1,5 +1,4 @@ import XAPI from "../../../../XAPI"; -import axios from "axios"; import { testActivity, testAgent, @@ -7,11 +6,10 @@ import { } from "../../../../../test/constants"; import { Resources } from "../../../../constants"; -jest.mock("axios"); - describe("state resource", () => { beforeEach(() => { - (axios as jest.MockedFunction).request.mockResolvedValueOnce({ + global.adapterFn.mockClear(); + global.adapterFn.mockResolvedValueOnce({ headers: { "content-type": "application/json", }, @@ -21,12 +19,13 @@ describe("state resource", () => { test("can delete all states", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); await xapi.deleteStates({ agent: testAgent, activityId: testActivity.id, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "DELETE", url: `${testEndpoint}${Resources.STATE}?agent=${encodeURIComponent( @@ -39,6 +38,7 @@ describe("state resource", () => { test("can delete all state for a registration", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); const testRegistration = "test-registration"; await xapi.deleteStates({ @@ -46,7 +46,7 @@ describe("state resource", () => { activityId: testActivity.id, registration: testRegistration, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "DELETE", url: `${testEndpoint}${Resources.STATE}?agent=${encodeURIComponent( @@ -61,6 +61,7 @@ describe("state resource", () => { test("can delete all states with etag", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); const testEtag = "my-etag"; await xapi.deleteStates({ @@ -68,7 +69,7 @@ describe("state resource", () => { activityId: testActivity.id, etag: testEtag, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "DELETE", headers: expect.objectContaining({ diff --git a/src/resources/document/state/getState/getState.int.test.ts b/src/resources/document/state/getState/getState.int.test.ts index 4f829b48..b0076a93 100644 --- a/src/resources/document/state/getState/getState.int.test.ts +++ b/src/resources/document/state/getState/getState.int.test.ts @@ -1,4 +1,4 @@ -import { v4 as uuidv4 } from "uuid"; +import crypto from "crypto"; import { testAgent, testActivity, @@ -31,7 +31,7 @@ forEachLRS((xapi) => { }); test("can get a state with a registration", () => { - const registration = uuidv4(); + const registration = crypto.randomUUID(); return xapi .createState({ agent: testAgent, diff --git a/src/resources/document/state/getState/getState.ts b/src/resources/document/state/getState/getState.ts index 7ad62a47..347c8b30 100644 --- a/src/resources/document/state/getState/getState.ts +++ b/src/resources/document/state/getState/getState.ts @@ -1,4 +1,4 @@ -import { AxiosPromise } from "axios"; +import { AdapterPromise } from "../../../../adapters"; import { Resources } from "../../../../constants"; import XAPI from "../../../../XAPI"; import { GetStateParams } from "./GetStateParams"; @@ -7,7 +7,7 @@ import { Document } from "../../Document"; export function getState( this: XAPI, params: GetStateParams -): AxiosPromise { +): AdapterPromise { return this.requestResource({ resource: Resources.STATE, queryParams: { diff --git a/src/resources/document/state/getState/getState.unit.test.ts b/src/resources/document/state/getState/getState.unit.test.ts index 226830b5..e021731b 100644 --- a/src/resources/document/state/getState/getState.unit.test.ts +++ b/src/resources/document/state/getState/getState.unit.test.ts @@ -1,5 +1,4 @@ import XAPI from "../../../../XAPI"; -import axios from "axios"; import { testActivity, testAgent, @@ -8,11 +7,10 @@ import { } from "../../../../../test/constants"; import { Resources } from "../../../../constants"; -jest.mock("axios"); - describe("state resource", () => { beforeEach(() => { - (axios as jest.MockedFunction).request.mockResolvedValueOnce({ + global.adapterFn.mockClear(); + global.adapterFn.mockResolvedValueOnce({ headers: { "content-type": "application/json", }, @@ -22,13 +20,14 @@ describe("state resource", () => { test("can get a state", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); await xapi.getState({ agent: testAgent, activityId: testActivity.id, stateId: testStateId, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "GET", url: `${testEndpoint}${Resources.STATE}?agent=${encodeURIComponent( @@ -43,6 +42,7 @@ describe("state resource", () => { test("can get a state with a registration", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); const testRegistration = "test-registration"; await xapi.getState({ @@ -51,7 +51,7 @@ describe("state resource", () => { stateId: testStateId, registration: testRegistration, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "GET", url: `${testEndpoint}${Resources.STATE}?agent=${encodeURIComponent( @@ -68,6 +68,7 @@ describe("state resource", () => { test("can get a state with cache buster", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); await xapi.getState({ agent: testAgent, @@ -75,7 +76,7 @@ describe("state resource", () => { stateId: testStateId, useCacheBuster: true, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "GET", url: expect.stringContaining( diff --git a/src/resources/document/state/getStates/getStates.int.test.ts b/src/resources/document/state/getStates/getStates.int.test.ts index 25b100ef..06941edd 100644 --- a/src/resources/document/state/getStates/getStates.int.test.ts +++ b/src/resources/document/state/getStates/getStates.int.test.ts @@ -1,4 +1,4 @@ -import { v4 as uuidv4 } from "uuid"; +import crypto from "crypto"; import { testAgent, testActivity, @@ -22,7 +22,7 @@ forEachLRS((xapi) => { }); test("can get all states with a registration", () => { - const registration = uuidv4(); + const registration = crypto.randomUUID(); const stateId = new Date().getTime().toString(); return xapi .createState({ diff --git a/src/resources/document/state/getStates/getStates.ts b/src/resources/document/state/getStates/getStates.ts index 145dd959..8a683d8b 100644 --- a/src/resources/document/state/getStates/getStates.ts +++ b/src/resources/document/state/getStates/getStates.ts @@ -1,4 +1,4 @@ -import { AxiosPromise } from "axios"; +import { AdapterPromise } from "../../../../adapters"; import { Resources } from "../../../../constants"; import XAPI from "../../../../XAPI"; import { GetStatesParams } from "./GetStatesParams"; @@ -6,7 +6,7 @@ import { GetStatesParams } from "./GetStatesParams"; export function getStates( this: XAPI, params: GetStatesParams -): AxiosPromise { +): AdapterPromise { return this.requestResource({ resource: Resources.STATE, queryParams: { diff --git a/src/resources/document/state/getStates/getStates.unit.test.ts b/src/resources/document/state/getStates/getStates.unit.test.ts index 82538580..379926a3 100644 --- a/src/resources/document/state/getStates/getStates.unit.test.ts +++ b/src/resources/document/state/getStates/getStates.unit.test.ts @@ -1,5 +1,4 @@ import XAPI from "../../../../XAPI"; -import axios from "axios"; import { testActivity, testAgent, @@ -7,11 +6,10 @@ import { } from "../../../../../test/constants"; import { Resources } from "../../../../constants"; -jest.mock("axios"); - describe("state resource", () => { beforeEach(() => { - (axios as jest.MockedFunction).request.mockResolvedValueOnce({ + global.adapterFn.mockClear(); + global.adapterFn.mockResolvedValueOnce({ headers: { "content-type": "application/json", }, @@ -21,12 +19,13 @@ describe("state resource", () => { test("can get all states", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); await xapi.getStates({ agent: testAgent, activityId: testActivity.id, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "GET", url: `${testEndpoint}${Resources.STATE}?agent=${encodeURIComponent( @@ -39,6 +38,7 @@ describe("state resource", () => { test("can get all states for a registration", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); const testRegistration = "test-registration"; await xapi.getStates({ @@ -46,7 +46,7 @@ describe("state resource", () => { activityId: testActivity.id, registration: testRegistration, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "GET", url: `${testEndpoint}${Resources.STATE}?agent=${encodeURIComponent( @@ -61,6 +61,7 @@ describe("state resource", () => { test("can get all states since a certain date", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); const since = new Date(); since.setDate(since.getDate() - 1); // yesterday @@ -69,7 +70,7 @@ describe("state resource", () => { activityId: testActivity.id, since: since.toISOString(), }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "GET", url: `${testEndpoint}${Resources.STATE}?agent=${encodeURIComponent( @@ -84,13 +85,14 @@ describe("state resource", () => { test("can get all states with cache buster", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); await xapi.getStates({ agent: testAgent, activityId: testActivity.id, useCacheBuster: true, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "GET", url: expect.stringContaining( diff --git a/src/resources/document/state/setState/setState.int.test.ts b/src/resources/document/state/setState/setState.int.test.ts index 55c4304e..489ae274 100644 --- a/src/resources/document/state/setState/setState.int.test.ts +++ b/src/resources/document/state/setState/setState.int.test.ts @@ -1,4 +1,4 @@ -import { v4 as uuidv4 } from "uuid"; +import crypto from "crypto"; import { testAgent, testActivity, @@ -31,7 +31,7 @@ forEachLRS((xapi) => { activityId: testActivity.id, stateId: testStateId, state: testDocument, - registration: uuidv4(), + registration: crypto.randomUUID(), }) .then((result) => { return expect(result.data).toBeDefined(); diff --git a/src/resources/document/state/setState/setState.ts b/src/resources/document/state/setState/setState.ts index 59821e8e..d348f67d 100644 --- a/src/resources/document/state/setState/setState.ts +++ b/src/resources/document/state/setState/setState.ts @@ -1,4 +1,4 @@ -import { AxiosPromise } from "axios"; +import { AdapterPromise } from "../../../../adapters"; import { Resources } from "../../../../constants"; import XAPI from "../../../../XAPI"; import { SetStateParams } from "./SetStateParams"; @@ -6,7 +6,7 @@ import { SetStateParams } from "./SetStateParams"; export function setState( this: XAPI, params: SetStateParams -): AxiosPromise { +): AdapterPromise { const headers = {}; if (params.etag && params.matchHeader) headers[params.matchHeader] = params.etag; diff --git a/src/resources/document/state/setState/setState.unit.test.ts b/src/resources/document/state/setState/setState.unit.test.ts index bdfaa8eb..6073b42f 100644 --- a/src/resources/document/state/setState/setState.unit.test.ts +++ b/src/resources/document/state/setState/setState.unit.test.ts @@ -1,5 +1,4 @@ import XAPI from "../../../../XAPI"; -import axios from "axios"; import { testActivity, testAgent, @@ -9,11 +8,10 @@ import { } from "../../../../../test/constants"; import { Resources } from "../../../../constants"; -jest.mock("axios"); - describe("state resource", () => { beforeEach(() => { - (axios as jest.MockedFunction).request.mockResolvedValueOnce({ + global.adapterFn.mockClear(); + global.adapterFn.mockResolvedValueOnce({ headers: { "content-type": "application/json", }, @@ -23,6 +21,7 @@ describe("state resource", () => { test("can set state", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); await xapi.setState({ agent: testAgent, @@ -30,7 +29,7 @@ describe("state resource", () => { stateId: testStateId, state: testDocument, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "PUT", url: `${testEndpoint}${Resources.STATE}?agent=${encodeURIComponent( @@ -46,6 +45,7 @@ describe("state resource", () => { test("can set state with content type", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); const plainTextContentType = "text/plain"; await xapi.setState({ @@ -55,7 +55,7 @@ describe("state resource", () => { state: testDocument.test, contentType: plainTextContentType, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "PUT", headers: expect.objectContaining({ @@ -74,6 +74,7 @@ describe("state resource", () => { test("can set state with registration", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); const testRegistration = "test-registration"; await xapi.setState({ @@ -83,7 +84,7 @@ describe("state resource", () => { state: testDocument, registration: testRegistration, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "PUT", url: `${testEndpoint}${Resources.STATE}?agent=${encodeURIComponent( @@ -101,6 +102,7 @@ describe("state resource", () => { test("can set state with etag and match header", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); const testEtag = "my-etag"; const testMatchHeader = "If-Match"; @@ -112,7 +114,7 @@ describe("state resource", () => { etag: testEtag, matchHeader: testMatchHeader, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "PUT", headers: expect.objectContaining({ diff --git a/src/resources/statement/getMoreStatements/getMoreStatements.ts b/src/resources/statement/getMoreStatements/getMoreStatements.ts index 2f2e9529..3e7213d2 100644 --- a/src/resources/statement/getMoreStatements/getMoreStatements.ts +++ b/src/resources/statement/getMoreStatements/getMoreStatements.ts @@ -1,4 +1,4 @@ -import { AxiosPromise } from "axios"; +import { AdapterPromise } from "../../../adapters"; import XAPI from "../../../XAPI"; import { StatementsResponse, StatementsResponseWithAttachments } from ".."; import { GetMoreStatementsParams } from "./GetMoreStatementsParams"; @@ -6,7 +6,7 @@ import { GetMoreStatementsParams } from "./GetMoreStatementsParams"; export function getMoreStatements( this: XAPI, params: GetMoreStatementsParams -): AxiosPromise { +): AdapterPromise { const endpoint = new URL(this.endpoint); const url = `${endpoint.protocol}//${endpoint.host}${params.more}`; return this.requestURL(url); diff --git a/src/resources/statement/getMoreStatements/getMoreStatements.unit.test.ts b/src/resources/statement/getMoreStatements/getMoreStatements.unit.test.ts index 60f70b91..6fe93ae8 100644 --- a/src/resources/statement/getMoreStatements/getMoreStatements.unit.test.ts +++ b/src/resources/statement/getMoreStatements/getMoreStatements.unit.test.ts @@ -1,12 +1,10 @@ import XAPI from "../../../XAPI"; -import axios from "axios"; import { testEndpoint } from "../../../../test/constants"; -jest.mock("axios"); - describe("statement resource", () => { beforeEach(() => { - (axios as jest.MockedFunction).request.mockResolvedValueOnce({ + global.adapterFn.mockClear(); + global.adapterFn.mockResolvedValueOnce({ headers: { "content-type": "application/json", }, @@ -16,13 +14,14 @@ describe("statement resource", () => { test("can get more statements", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); const endpoint = new URL(testEndpoint); const testMoreIrl = "?more=test-more-irl"; await xapi.getMoreStatements({ more: testMoreIrl, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "GET", url: `${endpoint.protocol}//${endpoint.host}${testMoreIrl}`, diff --git a/src/resources/statement/getStatement/getStatement.ts b/src/resources/statement/getStatement/getStatement.ts index 9b9523b4..cb34dc57 100644 --- a/src/resources/statement/getStatement/getStatement.ts +++ b/src/resources/statement/getStatement/getStatement.ts @@ -1,4 +1,4 @@ -import { AxiosPromise } from "axios"; +import { AdapterPromise } from "../../../adapters"; import { Resources } from "../../../constants"; import XAPI from "../../../XAPI"; import { StatementResponseWithAttachments, Statement } from ".."; @@ -11,17 +11,17 @@ import { export function getStatement( this: XAPI, params: GetStatementParamsWithAttachments -): AxiosPromise; +): AdapterPromise; export function getStatement( this: XAPI, params: GetStatementParamsWithoutAttachments -): AxiosPromise; +): AdapterPromise; export function getStatement( this: XAPI, params: GetStatementParams -): AxiosPromise { +): AdapterPromise { return this.requestResource({ resource: Resources.STATEMENT, queryParams: { diff --git a/src/resources/statement/getStatement/getStatement.unit.test.ts b/src/resources/statement/getStatement/getStatement.unit.test.ts index cc93e1c5..ad060a7a 100644 --- a/src/resources/statement/getStatement/getStatement.unit.test.ts +++ b/src/resources/statement/getStatement/getStatement.unit.test.ts @@ -1,5 +1,4 @@ import XAPI from "../../../XAPI"; -import axios from "axios"; import { testAttachmentContent, testEndpoint, @@ -8,11 +7,10 @@ import { } from "../../../../test/constants"; import { Resources } from "../../../constants"; -jest.mock("axios"); - describe("statement resource", () => { beforeEach(() => { - (axios as jest.MockedFunction).request.mockResolvedValueOnce({ + global.adapterFn.mockClear(); + global.adapterFn.mockResolvedValueOnce({ headers: { "content-type": "application/json", }, @@ -22,12 +20,13 @@ describe("statement resource", () => { test("can get a single statement", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); const testStatementId = "test-statement-id"; await xapi.getStatement({ statementId: testStatementId, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "GET", url: `${testEndpoint}${Resources.STATEMENT}?statementId=${testStatementId}`, @@ -37,7 +36,8 @@ describe("statement resource", () => { test("can get a single statement with attachments", async () => { jest.resetAllMocks(); - (axios as jest.MockedFunction).request.mockResolvedValueOnce({ + global.adapterFn.mockClear(); + global.adapterFn.mockResolvedValueOnce({ headers: { "content-type": "multipart/mixed; boundary=", }, @@ -45,13 +45,14 @@ describe("statement resource", () => { }); const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); const testStatementId = "test-statement-id"; const result = await xapi.getStatement({ statementId: testStatementId, attachments: true, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "GET", url: `${testEndpoint}${Resources.STATEMENT}?statementId=${testStatementId}&attachments=true`, @@ -64,13 +65,14 @@ describe("statement resource", () => { test("can get a single statement with chosen format", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); const testStatementId = "test-statement-id"; await xapi.getStatement({ statementId: testStatementId, format: "canonical", }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "GET", url: `${testEndpoint}${Resources.STATEMENT}?statementId=${testStatementId}&format=canonical`, diff --git a/src/resources/statement/getStatements/getStatements.ts b/src/resources/statement/getStatements/getStatements.ts index f6bc5c33..c5212a30 100644 --- a/src/resources/statement/getStatements/getStatements.ts +++ b/src/resources/statement/getStatements/getStatements.ts @@ -1,4 +1,4 @@ -import { AxiosPromise } from "axios"; +import { AdapterPromise } from "../../../adapters"; import { Resources } from "../../../constants"; import XAPI from "../../../XAPI"; import { StatementsResponseWithAttachments, StatementsResponse } from ".."; @@ -11,17 +11,17 @@ import { export function getStatements( this: XAPI, params: GetStatementsParamsWithAttachments -): AxiosPromise; +): AdapterPromise; export function getStatements( this: XAPI, params?: GetStatementsParamsWithoutAttachments -): AxiosPromise; +): AdapterPromise; export function getStatements( this: XAPI, params?: GetStatementsParams -): AxiosPromise { +): AdapterPromise { return this.requestResource({ resource: Resources.STATEMENT, queryParams: { diff --git a/src/resources/statement/getStatements/getStatements.unit.test.ts b/src/resources/statement/getStatements/getStatements.unit.test.ts index 2c55dac7..b11a45c8 100644 --- a/src/resources/statement/getStatements/getStatements.unit.test.ts +++ b/src/resources/statement/getStatements/getStatements.unit.test.ts @@ -1,5 +1,4 @@ import XAPI from "../../../XAPI"; -import axios from "axios"; import { testActivity, testAgent, @@ -8,11 +7,10 @@ import { } from "../../../../test/constants"; import { Resources } from "../../../constants"; -jest.mock("axios"); - describe("statement resource", () => { beforeEach(() => { - (axios as jest.MockedFunction).request.mockResolvedValueOnce({ + global.adapterFn.mockClear(); + global.adapterFn.mockResolvedValueOnce({ headers: { "content-type": "application/json", }, @@ -22,9 +20,10 @@ describe("statement resource", () => { test("can get multiple statements", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); await xapi.getStatements(); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "GET", url: `${testEndpoint}${Resources.STATEMENT}`, @@ -35,11 +34,12 @@ describe("statement resource", () => { test("can get multiple statements with attachments", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); await xapi.getStatements({ attachments: true, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "GET", url: `${testEndpoint}${Resources.STATEMENT}?attachments=true`, @@ -50,6 +50,7 @@ describe("statement resource", () => { test("can get multiple statements with all query parameters", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); const testRegistration = "test-registration"; const since = new Date(); @@ -68,7 +69,7 @@ describe("statement resource", () => { until: since.toISOString(), verb: testVerb.id, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "GET", url: `${testEndpoint}${ @@ -89,11 +90,12 @@ describe("statement resource", () => { test("can get multiple statements with cache buster", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); await xapi.getStatements({ useCacheBuster: true, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "GET", url: expect.stringContaining( diff --git a/src/resources/statement/getVoidedStatement/getVoidedStatement.ts b/src/resources/statement/getVoidedStatement/getVoidedStatement.ts index 40ef978a..7d1d8691 100644 --- a/src/resources/statement/getVoidedStatement/getVoidedStatement.ts +++ b/src/resources/statement/getVoidedStatement/getVoidedStatement.ts @@ -1,4 +1,4 @@ -import { AxiosPromise } from "axios"; +import { AdapterPromise } from "../../../adapters"; import { Resources } from "../../../constants"; import XAPI from "../../../XAPI"; import { StatementResponseWithAttachments, Statement } from ".."; @@ -11,17 +11,17 @@ import { export function getVoidedStatement( this: XAPI, params: GetVoidedStatementParamsWithAttachments -): AxiosPromise; +): AdapterPromise; export function getVoidedStatement( this: XAPI, params: GetVoidedStatementParamsWithoutAttachments -): AxiosPromise; +): AdapterPromise; export function getVoidedStatement( this: XAPI, params: GetVoidedStatementParams -): AxiosPromise { +): AdapterPromise { return this.requestResource({ resource: Resources.STATEMENT, queryParams: { diff --git a/src/resources/statement/getVoidedStatement/getVoidedStatement.unit.test.ts b/src/resources/statement/getVoidedStatement/getVoidedStatement.unit.test.ts index 3e497e6b..076852b1 100644 --- a/src/resources/statement/getVoidedStatement/getVoidedStatement.unit.test.ts +++ b/src/resources/statement/getVoidedStatement/getVoidedStatement.unit.test.ts @@ -1,13 +1,11 @@ import XAPI from "../../../XAPI"; -import axios from "axios"; import { testEndpoint } from "../../../../test/constants"; import { Resources } from "../../../constants"; -jest.mock("axios"); - describe("statement resource", () => { beforeEach(() => { - (axios as jest.MockedFunction).request.mockResolvedValueOnce({ + global.adapterFn.mockClear(); + global.adapterFn.mockResolvedValueOnce({ headers: { "content-type": "application/json", }, @@ -17,12 +15,13 @@ describe("statement resource", () => { test("can get a voided statement", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); const testStatementId = "test-statement-id"; await xapi.getVoidedStatement({ voidedStatementId: testStatementId, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "GET", url: `${testEndpoint}${Resources.STATEMENT}?voidedStatementId=${testStatementId}`, @@ -33,13 +32,14 @@ describe("statement resource", () => { test("can get a voided statement with attachments", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); const testStatementId = "test-statement-id"; await xapi.getVoidedStatement({ voidedStatementId: testStatementId, attachments: true, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "GET", url: `${testEndpoint}${Resources.STATEMENT}?voidedStatementId=${testStatementId}&attachments=true`, @@ -50,13 +50,14 @@ describe("statement resource", () => { test("can get a voided statement with chosen format", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); const testStatementId = "test-statement-id"; await xapi.getVoidedStatement({ voidedStatementId: testStatementId, format: "canonical", }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "GET", url: `${testEndpoint}${Resources.STATEMENT}?voidedStatementId=${testStatementId}&format=canonical`, @@ -67,13 +68,14 @@ describe("statement resource", () => { test("can get a voided statement with cache buster", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); const testStatementId = "test-statement-id"; await xapi.getVoidedStatement({ voidedStatementId: testStatementId, useCacheBuster: true, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "GET", url: expect.stringContaining( diff --git a/src/resources/statement/sendStatement/sendStatement.ts b/src/resources/statement/sendStatement/sendStatement.ts index 31670b49..5dd68cc0 100644 --- a/src/resources/statement/sendStatement/sendStatement.ts +++ b/src/resources/statement/sendStatement/sendStatement.ts @@ -1,4 +1,4 @@ -import { AxiosPromise } from "axios"; +import { AdapterPromise } from "../../../adapters"; import { Resources } from "../../../constants"; import { createMultiPart, MultiPart } from "../../../internal/multiPart"; import XAPI from "../../../XAPI"; @@ -7,7 +7,7 @@ import { SendStatementParams } from "./SendStatementParams"; export function sendStatement( this: XAPI, params: SendStatementParams -): AxiosPromise { +): AdapterPromise { const hasAttachments = params.attachments?.length; if (hasAttachments) { const multiPart: MultiPart = createMultiPart( diff --git a/src/resources/statement/sendStatement/sendStatement.unit.test.ts b/src/resources/statement/sendStatement/sendStatement.unit.test.ts index c990b228..72482ce9 100644 --- a/src/resources/statement/sendStatement/sendStatement.unit.test.ts +++ b/src/resources/statement/sendStatement/sendStatement.unit.test.ts @@ -1,5 +1,4 @@ import XAPI from "../../../XAPI"; -import axios from "axios"; import { testAttachmentArrayBuffer, testEndpoint, @@ -10,11 +9,10 @@ import { Resources } from "../../../constants"; import { createMultiPart } from "../../../internal/multiPart"; import { testIf, isNode } from "../../../../test/jestUtils"; -jest.mock("axios"); - describe("statement resource", () => { beforeEach(() => { - (axios as jest.MockedFunction).request.mockResolvedValueOnce({ + global.adapterFn.mockClear(); + global.adapterFn.mockResolvedValueOnce({ headers: { "content-type": "application/json", }, @@ -24,11 +22,12 @@ describe("statement resource", () => { test("can send a statement", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); await xapi.sendStatement({ statement: testStatement, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "POST", url: `${testEndpoint}${Resources.STATEMENT}`, @@ -42,12 +41,13 @@ describe("statement resource", () => { async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); await xapi.sendStatement({ statement: testStatementWithEmbeddedAttachments, attachments: [testAttachmentArrayBuffer], }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "POST", headers: expect.objectContaining({ diff --git a/src/resources/statement/sendStatements/sendStatements.ts b/src/resources/statement/sendStatements/sendStatements.ts index 7dda789f..a5645675 100644 --- a/src/resources/statement/sendStatements/sendStatements.ts +++ b/src/resources/statement/sendStatements/sendStatements.ts @@ -1,4 +1,4 @@ -import { AxiosPromise } from "axios"; +import { AdapterPromise } from "../../../adapters"; import { Resources } from "../../../constants"; import { createMultiPart, MultiPart } from "../../../internal/multiPart"; import XAPI from "../../../XAPI"; @@ -7,7 +7,7 @@ import { SendStatementsParams } from "./SendStatementsParams"; export function sendStatements( this: XAPI, params: SendStatementsParams -): AxiosPromise { +): AdapterPromise { const hasAttachments = params.attachments?.length; if (hasAttachments) { const multiPart: MultiPart = createMultiPart( diff --git a/src/resources/statement/sendStatements/sendStatements.unit.test.ts b/src/resources/statement/sendStatements/sendStatements.unit.test.ts index 4a053968..65f2d14c 100644 --- a/src/resources/statement/sendStatements/sendStatements.unit.test.ts +++ b/src/resources/statement/sendStatements/sendStatements.unit.test.ts @@ -1,5 +1,4 @@ import XAPI from "../../../XAPI"; -import axios from "axios"; import { testAttachmentArrayBuffer, testEndpoint, @@ -10,11 +9,10 @@ import { Resources } from "../../../constants"; import { createMultiPart } from "../../../internal/multiPart"; import { testIf, isNode } from "../../../../test/jestUtils"; -jest.mock("axios"); - describe("statement resource", () => { beforeEach(() => { - (axios as jest.MockedFunction).request.mockResolvedValueOnce({ + global.adapterFn.mockClear(); + global.adapterFn.mockResolvedValueOnce({ headers: { "content-type": "application/json", }, @@ -24,11 +22,12 @@ describe("statement resource", () => { test("can send multiple statements", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); await xapi.sendStatements({ statements: [testStatement, testStatement], }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "POST", url: `${testEndpoint}${Resources.STATEMENT}`, @@ -42,6 +41,7 @@ describe("statement resource", () => { async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); await xapi.sendStatements({ statements: [ @@ -50,7 +50,7 @@ describe("statement resource", () => { ], attachments: [testAttachmentArrayBuffer, testAttachmentArrayBuffer], }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "POST", headers: expect.objectContaining({ diff --git a/src/resources/statement/voidStatement/voidStatement.ts b/src/resources/statement/voidStatement/voidStatement.ts index 00823a7c..1f877b6b 100644 --- a/src/resources/statement/voidStatement/voidStatement.ts +++ b/src/resources/statement/voidStatement/voidStatement.ts @@ -1,4 +1,4 @@ -import { AxiosPromise } from "axios"; +import { AdapterPromise } from "../../../adapters"; import { Verbs } from "../../../constants"; import XAPI from "../../../XAPI"; import { Statement } from ".."; @@ -7,7 +7,7 @@ import { VoidStatementParams } from "./VoidStatementParams"; export function voidStatement( this: XAPI, params: VoidStatementParams -): AxiosPromise { +): AdapterPromise { const voidStatement: Statement = { actor: params.actor, verb: Verbs.VOIDED, diff --git a/src/resources/statement/voidStatement/voidStatement.unit.test.ts b/src/resources/statement/voidStatement/voidStatement.unit.test.ts index c8cf3cf6..97cebe46 100644 --- a/src/resources/statement/voidStatement/voidStatement.unit.test.ts +++ b/src/resources/statement/voidStatement/voidStatement.unit.test.ts @@ -1,5 +1,4 @@ import XAPI from "../../../XAPI"; -import axios from "axios"; import { testAgent, testEndpoint, @@ -8,11 +7,10 @@ import { import { Resources, Verbs } from "../../../constants"; import { Statement } from ".."; -jest.mock("axios"); - describe("statement resource", () => { beforeEach(() => { - (axios as jest.MockedFunction).request.mockResolvedValueOnce({ + global.adapterFn.mockClear(); + global.adapterFn.mockResolvedValueOnce({ headers: { "content-type": "application/json", }, @@ -22,12 +20,13 @@ describe("statement resource", () => { test("can void a statement", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); await xapi.voidStatement({ actor: testAgent, statementId: testStatement.id, }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "POST", url: `${testEndpoint}${Resources.STATEMENT}`, diff --git a/src/resources/statement/voidStatements/voidStatements.ts b/src/resources/statement/voidStatements/voidStatements.ts index ef3ed831..8f7ba1c0 100644 --- a/src/resources/statement/voidStatements/voidStatements.ts +++ b/src/resources/statement/voidStatements/voidStatements.ts @@ -1,4 +1,4 @@ -import { AxiosPromise } from "axios"; +import { AdapterPromise } from "../../../adapters"; import { Verbs } from "../../../constants"; import XAPI from "../../../XAPI"; import { Statement } from ".."; @@ -7,7 +7,7 @@ import { VoidStatementsParams } from "./VoidStatementsParams"; export function voidStatements( this: XAPI, params: VoidStatementsParams -): AxiosPromise { +): AdapterPromise { const voidStatements: Statement[] = params.statementIds.map((statementId) => { return { actor: params.actor, diff --git a/src/resources/statement/voidStatements/voidStatements.unit.test.ts b/src/resources/statement/voidStatements/voidStatements.unit.test.ts index 983e6e63..997576e2 100644 --- a/src/resources/statement/voidStatements/voidStatements.unit.test.ts +++ b/src/resources/statement/voidStatements/voidStatements.unit.test.ts @@ -1,5 +1,4 @@ import XAPI from "../../../XAPI"; -import axios from "axios"; import { testAgent, testEndpoint, @@ -9,11 +8,10 @@ import { import { Resources, Verbs } from "../../../constants"; import { Statement } from ".."; -jest.mock("axios"); - describe("statement resource", () => { beforeEach(() => { - (axios as jest.MockedFunction).request.mockResolvedValueOnce({ + global.adapterFn.mockClear(); + global.adapterFn.mockResolvedValueOnce({ headers: { "content-type": "application/json", }, @@ -23,12 +21,13 @@ describe("statement resource", () => { test("can void multiple statements", async () => { const xapi = new XAPI({ endpoint: testEndpoint, + adapter: global.adapter, }); await xapi.voidStatements({ actor: testAgent, statementIds: [testStatement.id, testStatementWithEmbeddedAttachments.id], }); - expect(axios.request).toHaveBeenCalledWith( + expect(global.adapterFn).toHaveBeenCalledWith( expect.objectContaining({ method: "POST", url: `${testEndpoint}${Resources.STATEMENT}`, diff --git a/test/getCredentials.ts b/test/getCredentials.ts index dc130216..90ee4e43 100644 --- a/test/getCredentials.ts +++ b/test/getCredentials.ts @@ -23,6 +23,7 @@ export function forEachLRS( const xapi: XAPI = new XAPI({ endpoint: credential.endpoint, auth: auth, + adapter: global.adapter, }); callbackfn(xapi, credential); }); diff --git a/test/jestUtils.ts b/test/jestUtils.ts index 4d9c7641..455ad4e4 100644 --- a/test/jestUtils.ts +++ b/test/jestUtils.ts @@ -2,4 +2,7 @@ const testIf = (condition: boolean): jest.It => (condition ? test : test.skip); const isNode = (): boolean => typeof window === "undefined"; -export { testIf, isNode }; +// @ts-expect-error EdgeRuntime is not present in local environment +const isEdgeRuntime = (): boolean => typeof EdgeRuntime !== "undefined"; + +export { testIf, isNode, isEdgeRuntime }; diff --git a/test/mockAxios.ts b/test/mockAxios.ts new file mode 100644 index 00000000..3d35729e --- /dev/null +++ b/test/mockAxios.ts @@ -0,0 +1,11 @@ +import axios from "axios"; + +jest.mock("axios"); + +(axios.request as any).mockImplementation(() => + Promise.resolve({ + headers: { + "content-type": "application/json", + }, + }) +); diff --git a/test/mockFetch.ts b/test/mockFetch.ts new file mode 100644 index 00000000..7cbfc6c9 --- /dev/null +++ b/test/mockFetch.ts @@ -0,0 +1,13 @@ +global.fetch = jest.fn(() => + Promise.resolve({ + json: () => + Promise.resolve({ + headers: { + "content-type": "application/json", + }, + }), + text: () => Promise.resolve(""), + ok: true, + headers: new Headers(), + } as Response) +); diff --git a/test/polyfillFetch.ts b/test/polyfillFetch.ts new file mode 100644 index 00000000..7a2808d5 --- /dev/null +++ b/test/polyfillFetch.ts @@ -0,0 +1 @@ +import "whatwg-fetch"; diff --git a/test/setupAxios.ts b/test/setupAxios.ts new file mode 100644 index 00000000..5d1cfbe1 --- /dev/null +++ b/test/setupAxios.ts @@ -0,0 +1,3 @@ +import * as axiosAdapter from "../src/adapters/axiosAdapter"; + +global.adapterFn = jest.spyOn(axiosAdapter, "default"); diff --git a/test/setupFetch.ts b/test/setupFetch.ts new file mode 100644 index 00000000..7a92d49a --- /dev/null +++ b/test/setupFetch.ts @@ -0,0 +1,4 @@ +import * as fetchAdapter from "../src/adapters/fetchAdapter"; + +global.adapter = "fetch"; +global.adapterFn = jest.spyOn(fetchAdapter, "default"); diff --git a/test/setupInt.ts b/test/setupInt.ts new file mode 100644 index 00000000..5a7b9171 --- /dev/null +++ b/test/setupInt.ts @@ -0,0 +1,4 @@ +beforeEach(() => { + // Add artificial delay between integration tests to avoid rate limit in SCORM Cloud + return new Promise((resolve) => setTimeout(() => resolve(null), 250)); +});