Browse Source

headache causing types

tags/v1.0.0-alpha.0
ojizero 1 year ago
parent
commit
72d117bb31
No account linked to committer's email address
4 changed files with 178 additions and 30 deletions
  1. 48
    20
      src/client.ts
  2. 18
    5
      src/method.ts
  3. 111
    4
      test/client.spec.ts
  4. 1
    1
      test/method.spec.ts

+ 48
- 20
src/client.ts View File

@@ -3,9 +3,10 @@ import querystring from 'querystring'
import { IncomingHttpHeaders } from 'http';
import { OutgoingHttpHeaders } from 'http'
import defaultsDeep from 'lodash.defaultsdeep'
import { RequestOptions } from 'https';
import { RequestOptions as HttpsRequestOptions } from 'https';

export enum AuthenticationTypes {
None = 'none',
BasicAuth = 'basic',
// TokenAuth = 'token',
// ApiKeyAuth = 'key',
@@ -30,6 +31,13 @@ interface AuthSpec {

export type Seconds = number

export interface RawResponse {
body: any,
statusCode?: number,
statusMessage?: string,
headers: IncomingHttpHeaders,
}

export interface Response {
status: {
code?: number,
@@ -37,7 +45,17 @@ export interface Response {
},
body: any,
headers: IncomingHttpHeaders,
_rawResponse: got.Response<any>, // raw string response
_rawResponse: RawResponse,
}

// Add compatibility with Got type
export interface RequestOptions extends HttpsRequestOptions {
baseUrl: string,
url: string,
json: boolean,
body: string,
retries?: number,
throwHttpErrors: boolean,
}

export interface Config {
@@ -46,16 +64,14 @@ export interface Config {
// port?: number,
headers?: OutgoingHttpHeaders,
authentication?: Authentication,
retries?: number,
timeout?: Seconds,
retries?: number, // available from RequestOptions
timeout?: Seconds, // available from HttpsRequestOptions
onError?: 'reject' | 'resolve',
}

export type RawResponse = got.Response<any>

export type ClientFn = (options: RequestOptions) => Promise<RawResponse>

// export type RequestOptions = Config & RequestConfig // TODO:
export interface RequestConfig extends RequestOptions, Config {}

export interface Client {
request (method: string, path: string, payload: {}, options: any): Promise<Response>
@@ -70,23 +86,15 @@ export class PortalClient implements Client {
this.config = config
}

async request (method: string, path: string, payload: {}, options: any): Promise<Response> {
async request (method: string, path: string, payload: {}, options: RequestConfig): Promise<Response> {
const requestOptions = this.constructRequestOptions(method, path, payload, options)

const response = await this.client(requestOptions)

return {
status: {
code: response.statusCode,
word: response.statusMessage,
},
body: response.body, // TODO: should it be parsed if needed ?
headers: response.headers,
_rawResponse: response,
}
return this.transformResponse(response)
}

constructRequestOptions (method: string, path: string, payload: { [k: string]: any }, options: any): { [s: string]: any } {
constructRequestOptions (method: string, path: string, payload: { [k: string]: any }, options: RequestConfig): RequestOptions {
const {
baseUrl,
headers,
@@ -98,7 +106,7 @@ export class PortalClient implements Client {
// port: 443,
retries: 0,
headers: {},
timeout: 30_000,
timeout: 30,
onError: 'reject',
// protocol: 'https',
}, this.config, options)
@@ -143,6 +151,15 @@ export class PortalClient implements Client {

setupAuthentication (auth: Authentication): AuthSpec {
switch (auth.type) {
case AuthenticationTypes.None: {
return {
useHeader: false,
usePayload: false,
useQueryString: false,
key: '',
value: '',
}
}
case AuthenticationTypes.BasicAuth: {
const token = Buffer.from(`${auth.username}:${auth.password}`).toString('base64')

@@ -156,7 +173,6 @@ export class PortalClient implements Client {
}

case AuthenticationTypes.BearerAuth: {

return {
useHeader: true,
usePayload: false,
@@ -169,6 +185,18 @@ export class PortalClient implements Client {

throw new Error('TODO: givem me a meangingful error')
}

transformResponse(response: RawResponse): Response {
return {
status: {
code: response.statusCode,
word: response.statusMessage,
},
body: response.body, // TODO: should it be parsed if needed ?
headers: response.headers,
_rawResponse: response,
}
}
}

export default PortalClient

+ 18
- 5
src/method.ts View File

@@ -28,7 +28,7 @@ export function method (client: Client): MethodFactory {
return function methodGenerator (spec: MethodSpec): RouteFunction {
const {
path,
method = 'GET',
method: _method = 'GET',
params = undefined,
body = undefined,
queryString = undefined,
@@ -36,6 +36,8 @@ export function method (client: Client): MethodFactory {
accept = applicationJson,
} = spec

const method = _method.toUpperCase()

const defaultOptions = {
headers: {
'Accept': accept,
@@ -60,15 +62,26 @@ export function method (client: Client): MethodFactory {
payload = args[length - 1]
args = args.slice(0, length - 1)
length -= 1
} else if (typeof options !== 'undefined') {
payload = options
options = undefined
} else if (
// TODO: ??? does this make sense ?
(typeof body !== 'undefined' || method === 'POST' || method === 'PUT')
&& typeof options !== 'undefined'
) {
const optionsHasPayload = 'payload' in options

payload = optionsHasPayload ? options.payload : options
options = optionsHasPayload ? options : undefined
}

if (typeof queryString !== 'undefined' && typeof args[length - 1] === 'object') {
if (typeof args[length - 1] === 'object') {
query = args[length - 1]
args = args.slice(0, length - 1)
length -= 1
} else if (typeof queryString !== 'undefined' && typeof options !== 'undefined') {
const optionsHasQueryString = 'queryString' in options

query = optionsHasQueryString ? options.queryString : options
options = optionsHasQueryString ? options : undefined
} else if (typeof queryString !== 'undefined' && typeof payload !== 'undefined') {
query = payload
payload = undefined

+ 111
- 4
test/client.spec.ts View File

@@ -1,9 +1,116 @@
import Client from '../src/client'
/// <reference path='typings/globals.d.ts' />

import Client, { Config, RawResponse, Authentication, AuthenticationTypes, ClientFn, Response } from '../src/client'

const mockRawResponse: RawResponse = {
body: 'any',
headers: { some: 'header' },
statusCode: 200,
statusMessage: 'OK',
}

const mockTransformedResponse: Response = {
status: {
code: 200,
word: 'OK',
},
body: 'any',
headers: { some: 'header' },
_rawResponse: mockRawResponse,
}

const mockConfig: Config = {
baseUrl: 'https://dummy.domain',
}

const mockBearerAuthConfig: Authentication = {
type: AuthenticationTypes.BearerAuth,
authToken: 'some-token-value'
}

const mockBasicAuthConfig: Authentication = {
type: AuthenticationTypes.BasicAuth,
username: 'some-username',
password: 'some-password',
}

const mockAuthConfigNotSupported /*: Authentication*/ = {
type: 'invalid type'
}

describe('Client', () => {
let client
let rawClient: ClientFn
let client: Client

describe('Construct request options', () => {
before(() => {
rawClient = sinon.spy()
client = new Client(rawClient, mockConfig)
})

it('transforms request parameters with all defaults', () => {
//
})

it('adds additional headers from options', () => {
//
})

describe('Authentication setup', () => {
before(() => {
rawClient = sinon.spy()
client = new Client(rawClient, mockConfig)
})

it('adds basic authentication', () => {
//
})

it('adds bearer authentication', () => {
//
})
})
})

describe('Setup authentication', () => {
before(() => {
rawClient = sinon.spy()
client = new Client(rawClient, mockConfig)
})

it('throws for non-supported types', () => {
// TODO: unable to test it as Typescript won't allow me to even compile it
// expect(() => client.setupAuthentication(mockAuthConfigNotSupported))
// .to.throw()
})

it('transforms authentication spec for basic auth', () => {
expect(client.setupAuthentication(mockBasicAuthConfig))
.to.deep.equal({
key: 'Authorization',
useHeader: true,
usePayload: false,
useQueryString: false,
value: 'Basic c29tZS11c2VybmFtZTpzb21lLXBhc3N3b3Jk',
})
})

it('transforms authentication spec for bearer auth', () => {
expect(client.setupAuthentication(mockBearerAuthConfig))
.to.deep.equal({
key: 'Authorization',
useHeader: true,
usePayload: false,
useQueryString: false,
value: 'Bearer some-token-value',
})
})
})

it('transform raw response', () => {
client = new Client(sinon.spy(), mockConfig)

before(() => {
// client = new Client()
expect(client.transformResponse(mockRawResponse))
.to.deep.equal(mockTransformedResponse)
})
})

+ 1
- 1
test/method.spec.ts View File

@@ -50,7 +50,7 @@ describe('Method', async () => {
let methodFunction

before(() => {
client = { request (method, path, options) { return Promise.resolve() } }
client = { request: sinon.spy() }
})

it('accepts client and returns generator function', async () => {

Loading…
Cancel
Save