From 1220331d45a69aadd8363c2554741af985ebe703 Mon Sep 17 00:00:00 2001 From: ojizero Date: Sat, 22 Dec 2018 13:14:26 +0200 Subject: [PATCH] workout on method specification --- package-lock.json | 205 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 10 +++ src/client.ts | 4 +- src/method.ts | 78 +++++++++++++++--- src/validation.ts | 29 +++++++ test/method.spec.ts | 187 +++++++++++++++++++++++++++++++++++------- test/mocks/client.ts | 14 ---- test/setup.ts | 10 ++- test/typings/globals.d.ts | 2 + tsconfig.json | 1 + 10 files changed, 479 insertions(+), 61 deletions(-) create mode 100644 src/validation.ts delete mode 100644 test/mocks/client.ts diff --git a/package-lock.json b/package-lock.json index 1bb8215..0747c1d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -128,6 +128,35 @@ "to-fast-properties": "^2.0.0" } }, + "@sinonjs/commons": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.3.0.tgz", + "integrity": "sha512-j4ZwhaHmwsCb4DlDOIWnI5YyKDNMoNThsmwEpfHx6a1EpsGZ9qYLxP++LMlmBRjtGptGHFsGItJ768snllFWpA==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/formatio": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.1.0.tgz", + "integrity": "sha512-ZAR2bPHOl4Xg6eklUGpsdiIJ4+J1SNag1DHHrG/73Uz/nVwXqjgUtRPLoS+aVyieN9cSbc0E4LsU984tWcDyNg==", + "dev": true, + "requires": { + "@sinonjs/samsam": "^2 || ^3" + } + }, + "@sinonjs/samsam": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.0.2.tgz", + "integrity": "sha512-m08g4CS3J6lwRQk1pj1EO+KEVWbrbXsmi9Pw0ySmrIbcVxVaedoFgLvFsV8wHLwh01EpROVz3KvVcD1Jmks9FQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.0.2", + "array-from": "^2.1.1", + "lodash.get": "^4.4.2" + } + }, "@types/chai": { "version": "4.1.7", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.1.7.tgz", @@ -149,6 +178,27 @@ "integrity": "sha512-g0+CQosizg1hjNn06fKB2tEvS5kExrvVOkIfsGuIRfsQ/A9u/Xjp/6/czJVyLuCYdkmMbplDUXvQW+YjBQK7dA==", "dev": true }, + "@types/joi": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/@types/joi/-/joi-14.0.1.tgz", + "integrity": "sha512-0uZZ+nffpr480zwwUXsk0Z5O0szllffNW1EbkI+dDzKhNKhiX4QOwpwK37WpKIpaPLk9V8U9y2We/VOeD6zyhQ==", + "dev": true + }, + "@types/lodash": { + "version": "4.14.119", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.119.tgz", + "integrity": "sha512-Z3TNyBL8Vd/M9D9Ms2S3LmFq2sSMzahodD6rCS9V2N44HUMINb75jNkSuwAx7eo2ufqTdfOdtGQpNbieUjPQmw==", + "dev": true + }, + "@types/lodash.defaultsdeep": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/@types/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.4.tgz", + "integrity": "sha512-Y0HdW4etNMY+U+DfZBgew0c6QjWLk0tk8FESYzOtIgglLO4WFOgei20V3vOa2+p/Grcxgm39Ysp/SvY2OJajFw==", + "dev": true, + "requires": { + "@types/lodash": "*" + } + }, "@types/mocha": { "version": "5.2.5", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.5.tgz", @@ -161,6 +211,22 @@ "integrity": "sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==", "dev": true }, + "@types/sinon": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-7.0.2.tgz", + "integrity": "sha512-YvJOqPk4kh1eQyxuASDD4MDK27XWAhtw6hJ7rRayEOkkTpZkqDWpDb4OjLVzFGdapOuUgZdnqO+71Q3utCJtcA==", + "dev": true + }, + "@types/sinon-chai": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/sinon-chai/-/sinon-chai-3.2.2.tgz", + "integrity": "sha512-5zSs2AslzyPZdOsbm2NRtuSNAI2aTWzNKOHa/GRecKo7a5efYD7qGcPxMZXQDayVXT2Vnd5waXxBvV31eCZqiA==", + "dev": true, + "requires": { + "@types/chai": "*", + "@types/sinon": "*" + } + }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -170,6 +236,12 @@ "color-convert": "^1.9.0" } }, + "array-from": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", + "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", + "dev": true + }, "arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", @@ -363,6 +435,11 @@ "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", "dev": true }, + "hoek": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-6.1.2.tgz", + "integrity": "sha512-6qhh/wahGYZHFSFw12tBbJw5fsAhhwrrG/y3Cs0YMTv2WzMnL0oLPnQJjv1QJvEfylRSOFuP+xCu+tdx0tD16Q==" + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -379,6 +456,20 @@ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "isemail": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/isemail/-/isemail-3.2.0.tgz", + "integrity": "sha512-zKqkK+O+dGqevc93KNsbZ/TqTUFd46MwWjYOoMrjIMZ51eU7DtQG3Wmd9SQQT7i7RVnuTPEiYEWHU3MSbxC1Tg==", + "requires": { + "punycode": "2.x.x" + } + }, "istanbul-lib-coverage": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz", @@ -400,6 +491,16 @@ "semver": "^5.5.0" } }, + "joi": { + "version": "14.3.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-14.3.0.tgz", + "integrity": "sha512-0HKd1z8MWogez4GaU0LkY1FgW30vR2Kwy414GISfCU41OYgUC2GWpNe5amsvBZtDqPtt7DohykfOOMIw1Z5hvQ==", + "requires": { + "hoek": "6.x.x", + "isemail": "3.x.x", + "topo": "3.x.x" + } + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -412,12 +513,35 @@ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true }, + "just-extend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.0.2.tgz", + "integrity": "sha512-FrLwOgm+iXrPV+5zDU6Jqu4gCRXbWEQg2O3SKONsWE4w7AXFRkryS53bpWdaL9cNol+AmR3AEYz6kn+o0fCPnw==", + "dev": true + }, "lodash": { "version": "4.17.11", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", "dev": true }, + "lodash.defaultsdeep": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.0.tgz", + "integrity": "sha1-vsECT4WxvZbL6kBbI8FK1kQ6b4E=" + }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, + "lolex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-3.0.0.tgz", + "integrity": "sha512-hcnW80h3j2lbUfFdMArd5UPA/vxZJ+G8vobd+wg3nVEQA0EigStbYcrG030FJxL6xiDDPEkoMatV9xIh5OecQQ==", + "dev": true + }, "make-error": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", @@ -473,6 +597,27 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, + "nise": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.4.8.tgz", + "integrity": "sha512-kGASVhuL4tlAV0tvA34yJYZIVihrUt/5bDwpp4tTluigxUr2bBlJeDXmivb6NuEdFkqvdv/Ybb9dm16PSKUhtw==", + "dev": true, + "requires": { + "@sinonjs/formatio": "^3.1.0", + "just-extend": "^4.0.2", + "lolex": "^2.3.2", + "path-to-regexp": "^1.7.0", + "text-encoding": "^0.6.4" + }, + "dependencies": { + "lolex": { + "version": "2.7.5", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.7.5.tgz", + "integrity": "sha512-l9x0+1offnKKIzYVjyXU2SiwhXDLekRzKyhnbyldPHvC7BvLPVpdNUNR2KeMAiCN2D/kLNttZgQD5WjSxuBx3Q==", + "dev": true + } + } + }, "nyc": { "version": "13.1.0", "resolved": "https://registry.npmjs.org/nyc/-/nyc-13.1.0.tgz", @@ -1627,18 +1772,64 @@ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, + "path-to-regexp": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", + "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "dev": true, + "requires": { + "isarray": "0.0.1" + } + }, "pathval": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", "dev": true }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, "semver": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", "dev": true }, + "sinon": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.2.2.tgz", + "integrity": "sha512-WLagdMHiEsrRmee3jr6IIDntOF4kbI6N2pfbi8wkv50qaUQcBglkzkjtoOEbeJ2vf1EsrHhLI+5Ny8//WHdMoA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.2.0", + "@sinonjs/formatio": "^3.1.0", + "@sinonjs/samsam": "^3.0.2", + "diff": "^3.5.0", + "lolex": "^3.0.0", + "nise": "^1.4.7", + "supports-color": "^5.5.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "sinon-chai": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-3.3.0.tgz", + "integrity": "sha512-r2JhDY7gbbmh5z3Q62pNbrjxZdOAjpsqW/8yxAZRSqLZqowmfGZPGUZPFf3UX36NLis0cv8VEM5IJh9HgkSOAA==", + "dev": true + }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -1672,12 +1863,26 @@ "has-flag": "^3.0.0" } }, + "text-encoding": { + "version": "0.6.4", + "resolved": "http://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", + "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=", + "dev": true + }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", "dev": true }, + "topo": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/topo/-/topo-3.0.3.tgz", + "integrity": "sha512-IgpPtvD4kjrJ7CRA3ov2FhWQADwv+Tdqbsf1ZnPUSAtCJ9e1Z44MmoSGDXGk4IppoZA7jd/QRkNddlLJWlUZsQ==", + "requires": { + "hoek": "6.x.x" + } + }, "trim-right": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", diff --git a/package.json b/package.json index ded851f..323aa44 100644 --- a/package.json +++ b/package.json @@ -23,16 +23,26 @@ "url": "https://github.com/ojizero/portal/issues" }, "homepage": "https://github.com/ojizero/portal#readme", + "dependencies": { + "joi": "^14.3.0", + "lodash.defaultsdeep": "^4.6.0" + }, "devDependencies": { "@types/chai": "^4.1.7", "@types/chai-as-promised": "^7.1.0", "@types/expect": "^1.20.3", + "@types/joi": "^14.0.1", + "@types/lodash.defaultsdeep": "^4.6.4", "@types/mocha": "^5.2.5", "@types/node": "^10.12.18", + "@types/sinon": "^7.0.2", + "@types/sinon-chai": "^3.2.2", "chai": "^4.2.0", "chai-as-promised": "^7.1.1", "mocha": "^5.2.0", "nyc": "^13.1.0", + "sinon": "^7.2.2", + "sinon-chai": "^3.3.0", "ts-node": "^7.0.1", "typescript": "^3.2.2" } diff --git a/src/client.ts b/src/client.ts index f985e3f..802713b 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1,5 +1,5 @@ export interface Client { - request (method: string, path: string, options: any): void // TODO: + request (method: string, path: string, payload: {}, options: any): void // TODO: } export class PortalClient implements Client { @@ -7,7 +7,7 @@ export class PortalClient implements Client { // TODO: } - request (method: string, path: string, options: any) { + request (method: string, path: string, payload: {}, options: any) { // TODO: } } diff --git a/src/method.ts b/src/method.ts index c663bf3..b02e89e 100644 --- a/src/method.ts +++ b/src/method.ts @@ -1,12 +1,16 @@ import { Client } from './client' +import defaults from 'lodash.defaultsdeep' +import { ValdiationSpec, ensureValidData, Validator } from './validation'; + const applicationJson = 'application/json' export interface MethodSpec { path: string, method?: string, - body?: {}, - queryString?: {}, + params?: ValdiationSpec, + body?: ValdiationSpec, + queryString?: ValdiationSpec, contentType?: string, accept?: string, strict?: boolean, @@ -17,23 +21,71 @@ export function method (client: Client) { const { path, method = 'GET', - body = {}, - queryString = {}, + params = undefined, + body = undefined, + queryString = undefined, contentType = applicationJson, accept = applicationJson, - strict = true } = spec - // It's ok for args here to be of implicit type any[] - // @ts-ignore - return function (...args) { - // TODO: replace path params with args - // TODO: reject if args doesn't match path params if strict + const defaultOptions = { + headers: { + 'Accept': accept, + 'Content-Type': contentType, + } + } + + return async function (...args: any[]) { + let length = args.length + + let query + let payload + let options + + if (typeof args[length - 1] === 'object') { + options = args[length - 1] + args = args.slice(0, length - 1) + length -= 1 + } + + if (typeof args[length - 1] === 'object') { + payload = args[length - 1] + args = args.slice(0, length - 1) + length -= 1 + } else if (typeof options !== 'undefined') { + payload = options + options = undefined + } + + if (typeof queryString !== 'undefined' && typeof args[length - 1] === 'object') { + query = args[length - 1] + args = args.slice(0, length - 1) + length -= 1 + } else if (typeof queryString !== 'undefined' && typeof payload !== 'undefined') { + query = payload + payload = undefined + } + + ensureValidData(params, args) + ensureValidData(body, payload) + ensureValidData(queryString, query) + + let paramsCount = (path.match(/:[^\/]*/) || []).length + + const fullPath = args.reduce((acc, arg) => { + paramsCount -= 1 + + return acc.replace(/:[^\/]*/, arg) + }, path) + + if (paramsCount !== 0) throw new Error('TODO: give me a meangingful error') + + // TODO: add query + + options = defaults({}, defaultOptions, options) - // TODO: last argument is allowed to be object, JSON payload - // TODO: reject if body doesn't match body spec if strict - return client.request(method, path, {}) + return client.request(method, fullPath, payload, options) } } } diff --git a/src/validation.ts b/src/validation.ts new file mode 100644 index 0000000..e12d4c2 --- /dev/null +++ b/src/validation.ts @@ -0,0 +1,29 @@ +import { SchemaLike, validate } from 'joi' + +export interface Validator { + validate (data: any[] | {}): boolean +} + +function isCustomValidator (spec: any): spec is Validator { + return !!spec && !spec.isJoi && 'validate' in spec +} + +export function ensureValidData (spec: SchemaLike | Validator | undefined, data: any[]) { + if (!spec) return + + if (isCustomValidator(spec)) { + const valid = spec.validate(data) + + if (valid) return + } + + const { error } = validate(data, spec) + + if (!error) return + + throw new Error('TODO: give me a meangingful error') +} + +export { SchemaLike } + +export type ValdiationSpec = SchemaLike | Validator diff --git a/test/method.spec.ts b/test/method.spec.ts index 3cb7f56..95905da 100644 --- a/test/method.spec.ts +++ b/test/method.spec.ts @@ -1,59 +1,190 @@ /// -import method from '../src/method' +import method, { MethodSpec } from '../src/method' import { Client } from '../src/client' -import { MockClient } from './mocks/client' +import Joi from 'joi' -const mockMethodSpec = {} +const mockGetMethodNoParams: MethodSpec = { + path: '/mock-path', + accept: 'mock/type', + contentType: 'mock/type', +} -describe('Method', () => { - let mockClient: Client +const mockGetMethodWithParams: MethodSpec = { + path: '/mock-path/:param', + accept: 'mock/type', + contentType: 'mock/type', +} + +const mockPostMethodNoParams: MethodSpec = { + path: '/mock-path', + method: 'POST', + body: Joi.object({ + some: Joi.object({ mock: Joi.string().required() }).required() + }).required(), + accept: 'mock/type', + contentType: 'mock/type', +} + +const mockPostMethodWithParams: MethodSpec = { + path: '/mock-path/:param', + method: 'POST', + // TODO: specify body spec + accept: 'mock/type', + contentType: 'mock/type', +} + +const mockGetMethodWithQueryString: MethodSpec = { + path: '/mock-path/:param', + accept: 'mock/type', + contentType: 'mock/type', + queryString: { + some_arg: Joi.string().required() + } +} + +describe('Method', async () => { + let client: Client let methodGenerator let methodFunction before(() => { - mockClient = new MockClient() + client = { request (method, path, options) {} } }) - it('accepts client and returns generator function', () => { - methodGenerator = method(mockClient) + it('accepts client and returns generator function', async () => { + methodGenerator = method(client) - expect(true).to.deep.equal(true) - // expect(methodFunction).to.be.a.function() // TODO: + expect(methodGenerator).to.be.a('function') }) it('accepts a method specification and returns a method function', () =>{ - methodFunction = methodGenerator(mockMethodSpec) + methodFunction = methodGenerator(mockGetMethodNoParams) - // expect(methodFunction).to.be.a.function() // TODO: + expect(methodFunction).to.be.a('function') }) - describe('Method function', () => { - it('calls underlying request on client', () => { - methodFunction() + describe('Generated method function', async () => { + beforeEach(() => { + client = { request: sinon.spy() } + methodGenerator = method(client) + }) + + it('calls underlying request on client', async () => { + methodFunction = methodGenerator(mockGetMethodNoParams) + + await methodFunction() + + expect(client.request) + .to.have.been.calledOnceWithExactly( + 'GET', + '/mock-path', + undefined, + { + headers: { + 'Accept': mockGetMethodNoParams.accept, + 'Content-Type': mockGetMethodNoParams.contentType, + } + } + ) + }) + + it('requires path parameters if specified', async () => { + methodFunction = methodGenerator(mockGetMethodWithParams) + + return expect(methodFunction()).to.eventually.be.rejected + }) + + it('passes path parameters if specified', async () => { + methodFunction = methodGenerator(mockGetMethodWithParams) + + await methodFunction(10) + + expect(client.request) + .to.have.been.calledOnceWithExactly( + 'GET', + '/mock-path/10', + undefined, + { + headers: { + 'Accept': mockGetMethodNoParams.accept, + 'Content-Type': mockGetMethodNoParams.contentType, + } + } + ) + }) + + it('requires body arguments if specified', async () => { + methodFunction = methodGenerator(mockPostMethodNoParams) - // expect(someSpy).to.be.called.once.with.exaclty() // TODO: + return expect(methodFunction()).to.eventually.be.rejected }) - it('requires path parameters if specified', () => { - // methodFunction = undefined - // methodFunction() // should throw + it('passes body arguments if specified', async () => { + methodFunction = methodGenerator(mockPostMethodNoParams) + const payload = { some: { mock: 'payload '} } + + await methodFunction(payload) + + expect(client.request) + .to.have.been.calledOnceWithExactly( + 'POST', + '/mock-path', + payload, + { + headers: { + 'Accept': mockGetMethodNoParams.accept, + 'Content-Type': mockGetMethodNoParams.contentType, + } + } + ) }) - it('passes path parameters if specified', () => { - // methodFunction = undefined - // methodFunction(1) // should not throw + it('passes positional parameter and body arguments if specified', async () => { + methodFunction = methodGenerator(mockPostMethodWithParams) + const payload = { some: { mock: 'payload '} } + + await methodFunction(10, payload) + + expect(client.request) + .to.have.been.calledOnceWithExactly( + 'POST', + '/mock-path/10', + payload, + { + headers: { + 'Accept': mockGetMethodNoParams.accept, + 'Content-Type': mockGetMethodNoParams.contentType, + } + } + ) }) - it('requires body arguments if specified', () => { - // methodFunction = undefined - // methodFunction() // should throw + it('requires query string arguments if specified', async () => { + methodFunction = methodGenerator(mockGetMethodWithQueryString) + + return expect(methodFunction()).to.eventually.be.rejected }) - it('passes body arguments if specified', () => { - // methodFunction = undefined - // methodFunction({ some: 'payload' }) + it('passes query string arguments if specified', async () => { + methodFunction = methodGenerator(mockGetMethodWithQueryString) + const query = { some_arg: 'a-string' } + + await methodFunction(10, query) + + expect(client.request) + .to.have.been.calledOnceWithExactly( + 'GET', + '/mock-path/10?some_arg=a-string', + undefined, + { + headers: { + 'Accept': mockGetMethodNoParams.accept, + 'Content-Type': mockGetMethodNoParams.contentType, + } + } + ) }) }) }) diff --git a/test/mocks/client.ts b/test/mocks/client.ts deleted file mode 100644 index 5ea87f7..0000000 --- a/test/mocks/client.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Client } from '../../src/client' - - -export class MockClient implements Client { - constructor () { - // - } - - request (method: string, options: any) { - // - } -} - -// TODO: export spies as well diff --git a/test/setup.ts b/test/setup.ts index 7a7d1a1..ebaf774 100644 --- a/test/setup.ts +++ b/test/setup.ts @@ -1,13 +1,15 @@ /// import chai from 'chai' +import sinon from 'sinon' + +import sinonChai from 'sinon-chai' import chaiAsPromised from 'chai-as-promised' +chai.use(sinonChai) chai.use(chaiAsPromised) global.chai = chai -global.expect = chai.expect +global.sinon = sinon -console.log('**************') -console.log('* SETUP DONE *') -console.log('**************') +global.expect = chai.expect diff --git a/test/typings/globals.d.ts b/test/typings/globals.d.ts index 3a20075..cadd383 100644 --- a/test/typings/globals.d.ts +++ b/test/typings/globals.d.ts @@ -1,8 +1,10 @@ +declare const sinon: any declare const expect: any declare namespace NodeJS { interface Global { chai: any + sinon: any expect: any } } diff --git a/tsconfig.json b/tsconfig.json index bd62e7f..82a319d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,7 @@ "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ // "lib": [], /* Specify library files to be included in the compilation. */ + "lib": [ "es2015" ], "allowJs": false, "declaration": true, "declarationMap": true,