Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(test): adds and cover the utility functions for testing actual and expected responses #182

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
15b40fb
feat(utilities): add test helper utilities
Jun 27, 2024
01ac894
Merge branch 'master' of https://github.com/apimatic/apimatic-js-runt…
asadali214 Jun 28, 2024
90fd6f5
adds utility functions for headers matching and file retrieving
asadali214 Jun 28, 2024
c50784d
refactor: updates dependencies
asadali214 Jun 28, 2024
f32b9a0
feat: adds support for more types in requestBuilder.text()
asadali214 Jun 28, 2024
c718239
build: downgrade yarn lock file
asadali214 Jun 29, 2024
97ca2bc
refactor: check style fixes
asadali214 Jun 29, 2024
b3fd863
refactor: check style fix
asadali214 Jun 29, 2024
27f2845
feat: implement new utilities in test helper
Jul 9, 2024
7d09572
Merge branch 'master' into 177-add-test-helper-utilities-for-comparin…
asadali214 Jul 9, 2024
1760955
fix(schema): functionality of discriminated object
asadali214 Jul 10, 2024
77856b4
fix(schema): functionality of discriminated object with nullable oute…
asadali214 Jul 10, 2024
4ff7126
fix(typescript): add string enum handling
Jul 11, 2024
6b74313
Merge branch '177-add-test-helper-utilities-for-comparing-array-and-o…
asadali214 Jul 11, 2024
8c3eaf1
refactor: major refactoring of new requestBuilder functions
asadali214 Jul 11, 2024
4851877
fix: build issue in requestBuilder
asadali214 Jul 11, 2024
9dbf6ac
refactor(typescript): move test helper functionality in a new package
Jul 11, 2024
cd63db5
ci: updated pacakge version
asadali214 Jul 12, 2024
e510e1d
refactor(tests): refactores test-helper tests
Jul 12, 2024
fff67d5
Merge branch '177-add-test-helper-utilities-for-comparing-array-and-o…
Jul 12, 2024
4c4c3f9
fix: incorrect import issue
asadali214 Jul 12, 2024
7f9683a
refactor: formatting issues in testHelper.test
asadali214 Jul 12, 2024
02a39ae
fix: isSameAsFile function
asadali214 Jul 12, 2024
84a8669
fix: stream matching tests using mock http client
asadali214 Jul 14, 2024
5085c80
fix: stream fetching and matching
asadali214 Jul 15, 2024
833581f
feat: added new dependency information to all areas
asadali214 Jul 15, 2024
8acb66b
ci: capability to upload coverage report for test-helper
asadali214 Jul 15, 2024
4c19779
ci: remove axios from jest configuration of test-helper library
asadali214 Jul 15, 2024
baf65b8
build: merge master into current branch
asadali214 Jul 18, 2024
5653e73
docs: adds readme to test-helper package
asadali214 Jul 18, 2024
e226239
feat: renamed test-helper package and refactored utility functions
asadali214 Jul 21, 2024
c9841f3
ci: capability to upload coverage report for test-utilities
asadali214 Jul 21, 2024
896cad0
docs: apply new utilities changes to readme
asadali214 Jul 21, 2024
57204ec
Merge branch '177-add-test-helper-utilities-for-comparing-array-and-o…
asadali214 Jul 21, 2024
5ec731e
build: remove jest-extended dependency
asadali214 Jul 21, 2024
54880d5
refactor: refactored assertion function to reduce complexity
asadali214 Jul 21, 2024
cc41d16
fix: adds support for null and undefined matching
asadali214 Jul 21, 2024
64f6918
build: refactored rollup config
asadali214 Jul 21, 2024
095ed4f
refactor: streams matching function directly throws error now
asadali214 Jul 21, 2024
446bd9f
feat: adds a new utility to get buffer data
asadali214 Jul 22, 2024
e89af4d
refactor: rename test suite
asadali214 Jul 22, 2024
8dca2a4
refactor: removed unnecessary test
asadali214 Jul 22, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
run: yarn build

- name: Test
run: yarn test --ci --coverage --maxWorkers=2
run: yarn test

- name: Lint
run: yarn lint
Expand All @@ -49,4 +49,5 @@ jobs:
${{github.workspace}}/packages/http-headers/coverage/lcov.info:lcov
${{github.workspace}}/packages/http-query/coverage/lcov.info:lcov
${{github.workspace}}/packages/oauth-adapters/coverage/lcov.info:lcov
${{github.workspace}}/packages/schema/coverage/lcov.info:lcov
${{github.workspace}}/packages/schema/coverage/lcov.info:lcov
${{github.workspace}}/packages/test-utilities/coverage/lcov.info:lcov
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ They provide common runtime utilities needed by SDKs to make API calls and handl
| [@apimatic/http-query](packages/http-query) | HTTP Query utilities for apimatic-js-runtime libraries |
| [@apimatic/oauth-adapters](packages/oauth-adapters) | Provides pluggable adapters for OAuth 2.0 authentication schemes. |
| [@apimatic/xml-adapter](packages/xml-adapter) | Provides XML serialization and deserialization utilities for apimatic-js-runtime libraries. |
| [@apimatic/test-utilities](packages/test-utilities) | Provides assertion utilities for testing api calls. It can be plugged in as dev dependency to any library. |

[ci-badge]: https://github.com/apimatic/apimatic-js-runtime/actions/workflows/main.yml/badge.svg
[ci-url]: https://github.com/apimatic/apimatic-js-runtime/actions/workflows/main.yml
Expand Down
35 changes: 35 additions & 0 deletions packages/test-utilities/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# APIMatic Test Utilities Libary for JavaScript

> This library is currently in preview.

Provides assertion utilities for testing api calls. It can be plugged in as dev dependency to any library.

The exported helper functions include:

1. **getStreamData**: Get streaming data from a given URL.
2. **toBuffer**: Promise to create a Buffer instance from a NodeJS.ReadableStream or Blob.
3. **expectHeadersToMatch**: Compare actual headers with expected headers, ignoring case sensitivity.
4. **expectMatchingWithOptions**: Check whether the expected value is matching with the actual value, with the given comparison options.

This library is used as a dev-dependency by JavaScript SDKs generated by the [APIMatic Code Generator](http://www.apimatic.io).

## Builds

The following environments are supported:

1. Node.js v10+
1. Bundlers like Rollup or Webpack
1. Web browsers

To support multiple environments, we export various builds:

| Environment | Usage |
| --- | --- |
| Common.js | Import like this: `require('@apimatic/test-utilities')`. |
| ES Module | Import like this: `import { /* your imports */ } from '@apimatic/test-utilities'`. |
| Browsers | *Use script: `https://unpkg.com/@apimatic/test-utilities@VERSION/umd/schema.js` |
| Modern Browsers (supports ESM and uses modern JS) | *Use script: `https://unpkg.com/@apimatic/test-utilities@VERSION/umd/schema.esm.js` |

_* Don't forget to replace VERSION with the version number._

**Note**: We discourage importing files or modules directly from the package. These are likely to change in the future and should not be considered stable.
7 changes: 7 additions & 0 deletions packages/test-utilities/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const { jest: lernaAliases } = require('lerna-alias');

module.exports = {
preset: 'ts-jest',
moduleNameMapper: lernaAliases(),
coverageReporters: [['lcov', { projectRoot: '../../' }]]
};
66 changes: 66 additions & 0 deletions packages/test-utilities/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{
"name": "@apimatic/test-utilities",
"version": "0.0.0",
"description": "provides the assertion utilities",
"main": "lib/index.js",
"module": "lib/index.js",
"types": "lib/index.d.ts",
"engines": {
"node": ">=14.15.0 || >=16.0.0"
},
"scripts": {
"clean": "rm -rf lib es umd tsconfig.tsbuildinfo",
"test": "jest --passWithNoTests",
"build": "npm run clean && tsc && rollup -c && npm run annotate:es",
"annotate:es": "babel es --out-dir es --no-babelrc --plugins annotate-pure-calls",
"preversion": "npm run test",
"prepublishOnly": "npm run build",
"size": "size-limit",
"analyze": "size-limit --why",
"lint": "tslint --project .",
"lint:fix": "tslint --project . --fix",
"check-style": "prettier --check \"{src,test}/**/*.ts\"",
"check-style:fix": "prettier --write \"{src,test}/**/*.ts\""
},
"author": "APIMatic Ltd.",
"license": "ISC",
"size-limit": [
{
"path": "umd/schema.js",
"limit": "5 KB"
},
{
"path": "umd/schema.esm.js",
"limit": "5 KB"
}
],
"devDependencies": {
"@babel/cli": "^7.21.5",
"@babel/core": "^7.22.1",
"@rollup/plugin-terser": "^0.4.3",
"@size-limit/preset-small-lib": "^7.0.8",
"babel-plugin-annotate-pure-calls": "^0.4.0",
"jest": "^26.4.2",
"jsdom": "^19.0.0",
"jsdom-global": "^3.0.2",
"lerna-alias": "3.0.3-0",
"rollup": "^2.79.0",
"rollup-plugin-replace": "^2.2.0",
"rollup-plugin-typescript2": "^0.31.0",
"size-limit": "^7.0.8",
"ts-jest": "^26.4.0",
"typescript": "^4.1.2"
},
"dependencies": {
"@apimatic/core-interfaces": "^0.2.5",
"tslib": "^2.1.0"
},
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "[email protected]:apimatic/apimatic-js-runtime.git",
"directory": "packages/test-utilities"
}
}
32 changes: 32 additions & 0 deletions packages/test-utilities/rollup.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import typescript from 'rollup-plugin-typescript2';

const getTsPlugin = ({ declaration = true, target } = {}) =>
typescript({
clean: true,
tsconfigOverride: {
compilerOptions: {
declaration,
...(target && { target })
}
}
});

const getNpmConfig = ({ input, output, external }) => ({
input,
output,
preserveModules: true,
plugins: [getTsPlugin({ declaration: true })],
external
});

export default [
getNpmConfig({
input: 'src/index.ts',
output: [
{
dir: 'es',
format: 'esm'
}
]
})
];
124 changes: 124 additions & 0 deletions packages/test-utilities/src/assertionUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/**
* Interface defining options for subset comparison functions.
*/
export interface ExpectOptions {
/**
* Whether extra elements are allowed in the actual object or array.
* Default: false
*/
allowExtra?: boolean;

/**
* Note: only applicable to objects (always true for arrays)
* Whether to check primitive values for equality.
* Default: false
*/
checkValues?: boolean;

/**
* Note: only applicable to arrays (always false for objects)
* Whether elements in the actual array should be compared in order to the expected array.
* Default: false
*/
isOrdered?: boolean;
}

/**
* Compare actual headers with expected headers, ignoring case sensitivity.
* @param actualHeaders Actual headers received from the request.
* @param expectedHeaders Expected headers with values to match against actual headers.
*/
export function expectHeadersToMatch(
actualHeaders: Record<string, string>,
expectedHeaders: Record<string, Array<string | boolean>>
): void {
const lowerCasedHeaders = Object.keys(actualHeaders).reduce((acc, key) => {
acc[key.toLowerCase()] = actualHeaders[key];
return acc;
}, {} as Record<string, string>);

Object.entries(expectedHeaders).forEach(([expectedKey, expectedValue]) => {
const lowerCasedKey = expectedKey.toLowerCase();
expect(lowerCasedHeaders).toHaveProperty(lowerCasedKey);
if (expectedValue[1]) {
expect(lowerCasedHeaders[lowerCasedKey]).toBe(expectedValue[0]);
}
});
}

/**
* Check whether the expected value is matching with the actual value.
* @param expected Expected value.
* @param actual Actual value to be matched with expected value.
* @param options Options for comparison of actual value with expected value.
*/
export function expectMatchingWithOptions(
expected: any,
actual?: any,
options: ExpectOptions = {}
): void {
expect(typeof actual).toEqual(typeof expected);

const {
isOrdered = false,
checkValues = false,
allowExtra = false,
} = options;

checkIfMatching(expected, actual, isOrdered, checkValues);
// when extra values are not allowed in actual array or object,
// check by inverting actual and expected values.
allowExtra || checkIfMatching(actual, expected, isOrdered, checkValues);
}

/**
* Recursively checks if right object or array contains all the elements
* of left object or array.
* @param left Left value.
* @param right Right value to be matched with left value.
* @param isOrdered Whether to check order of elements in arrays.
* @param checkValues Whether to check values of each key in objects.
*/
function checkIfMatching(
left: any,
right: any,
isOrdered: boolean,
checkValues: boolean
): void {
function isObject(value: any): value is object {
return value !== null && typeof value === 'object';
}

if (Array.isArray(left) && Array.isArray(right)) {
checkArrays(left, right, isOrdered);
} else if (isObject(left) && isObject(right)) {
checkObjects(left, right, isOrdered, checkValues);
} else if (checkValues) {
expect(left).toEqual(right);
}
}

function checkArrays(left: any[], right: any[], isOrdered: boolean) {
if (isOrdered) {
// Check if right array is directly equal to a partial left array.
expect(right).toEqual(expect.objectContaining(left));
return;
}
// Or check if right array contains all elements from left array.
left.forEach((leftVal) => expect(right).toContainEqual(leftVal));
}

function checkObjects(
left: object,
right: object,
isOrdered: boolean,
checkValues: boolean
) {
const rightObjKeys = Object.keys(right);
Object.keys(left).forEach((key) => {
// Check if right object keys contains this key from left object.
expect(rightObjKeys).toContainEqual(key);
// Recursive checking for each element in left and right object.
checkIfMatching(left[key], right[key], isOrdered, checkValues);
});
}
2 changes: 2 additions & 0 deletions packages/test-utilities/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './streamUtils';
export * from './assertionUtils';
65 changes: 65 additions & 0 deletions packages/test-utilities/src/streamUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { HttpClientInterface } from '@apimatic/core-interfaces';
import { Readable } from 'stream';

/**
* Get streaming data from a given URL.
* @param client Instance of HttpClient to be used.
* @param url URL from which to create the readable stream.
* @returns Stream of data fetched from the URL.
* @throws Error if unable to retrieve data from the URL.
*/
export async function getStreamData(
client: HttpClientInterface,
url: string
): Promise<NodeJS.ReadableStream | Blob> {
const res = await client({ method: 'GET', url, responseType: 'stream' });
if (res.statusCode !== 200 || typeof res.body === 'string') {
throw new Error(`Unable to retrieve streaming data from ${url}`);
}
return res.body;
}

/**
* Convert a NodeJS ReadableStream or Blob to a Buffer.
* @param input NodeJS ReadableStream or Blob to convert.
* @returns Promise resolving to a Buffer containing the input data.
*/
export async function toBuffer(
input: NodeJS.ReadableStream | Blob | undefined
): Promise<Buffer> {
if (typeof Blob !== 'undefined' && input instanceof Blob) {
return blobToBuffer(input);
}
if (typeof Readable !== 'undefined' && input instanceof Readable) {
return streamToBuffer(input);
}
throw new Error('Unsupported input type. Expected a Blob or ReadableStream.');
}

/**
* Convert a NodeJS ReadableStream to a Buffer.
* @param stream Readable stream to convert.
* @returns Promise resolving to a Buffer containing stream data.
*/
async function streamToBuffer(stream: NodeJS.ReadableStream): Promise<Buffer> {
const chunks: Uint8Array[] = [];
for await (const chunk of stream) {
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
}
return Buffer.concat(chunks);
}

/**
* Convert a Blob to a Buffer.
* @param blob Blob to convert.
* @returns Promise resolving to an Buffer containing blob data.
*/
async function blobToBuffer(blob: Blob): Promise<Buffer> {
const arrayBuffer = new Promise<ArrayBuffer>((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result as ArrayBuffer);
reader.onerror = reject;
reader.readAsArrayBuffer(blob);
});
return Buffer.from(await arrayBuffer);
}
Loading
Loading