Skip to content

Commit

Permalink
Some small refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
mayorova committed Apr 12, 2023
1 parent b6a92e0 commit 986609e
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 69 deletions.
56 changes: 27 additions & 29 deletions app/javascript/src/ActiveDocs/ThreeScaleApiDocs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import SwaggerUI from 'swagger-ui'
import { execute } from 'swagger-client/es/execute'

import { fetchData } from 'utilities/fetchData'
import { safeFromJsonString } from 'utilities/json-utils'
import { autocompleteRequestInterceptor } from 'ActiveDocs/OAS3Autocomplete'

import type { ApiDocsServices, BackendApiReportBody, BackendApiTransaction, ExecuteData } from 'Types/SwaggerTypes'
import type { ApiDocsServices, BackendApiReportBody, BackendApiTransaction, BodyValue, BodyValueObject, FormData, ExecuteData } from 'Types/SwaggerTypes'
import type { SwaggerUIPlugin } from 'swagger-ui'

const getApiSpecUrl = (baseUrl: string, specPath: string): string => {
Expand All @@ -21,14 +22,9 @@ const appendSwaggerDiv = (container: HTMLElement, id: string): void => {
}

/**
* when using Record notation, the following error is thrown:
* 'TS2456: Type alias 'BodyValue' circularly references itself.'
*/
// eslint-disable-next-line @typescript-eslint/consistent-indexed-object-style
type BodyValue = boolean | number | string | { [key: string]: BodyValue }

/**
* Transforms an object into form data representation, also URL-encoding the values,
* Transforms an object into form data representation. Does not URL-encode, because it will be done by
* swagger-client itself
* Returns an empty object if the argument is not an object
* Example:
* {
* a_string: 'hello',
Expand All @@ -42,23 +38,27 @@ type BodyValue = boolean | number | string | { [key: string]: BodyValue }
* a_string: 'hello',
* 'an_array[0][first]': '1',
* 'an_array[1][second]': '1',
* 'an_array[1][extra_param]': 'with%20whitespace'
* 'an_array[1][extra_param]': 'with whitespace'
* }
* @param object
*/
export const objectToFormData = (object: BodyValue): Record<string, boolean | number | string> => {
const buildFormData = (formData: Record<string, boolean | number | string>, data: BodyValue, parentKey?: string) => {
export const objectToFormData = (object: BodyValue): FormData => {
if (typeof object !== 'object' || Array.isArray(object)) {
return {}
}
const buildFormData = (formData: FormData, data: BodyValue, parentKey?: string) => {
if (data && typeof data === 'object') {
Object.keys(data).forEach((key: string) => {
buildFormData(formData, data[key], parentKey ? `${parentKey}[${key}]` : key)
const dataObject = data as BodyValueObject
Object.keys(dataObject).forEach((key: string) => {
buildFormData(formData, dataObject[key], parentKey ? `${parentKey}[${key}]` : key)
})
} else {
if (parentKey) {
formData[parentKey] = data ? data : ''
}
}
}
const formData = {}
const formData: FormData = {}
buildFormData(formData, object)
return formData
}
Expand All @@ -74,22 +74,20 @@ export const objectToFormData = (object: BodyValue): Record<string, boolean | nu
* 'transactions[0][usage][hits]': 1
* @param body BackendApiReportBody
*/
export const transformReportRequestBody = (body: BackendApiReportBody): Record<string, boolean | number | string> => {
export const transformReportRequestBody = (body: BackendApiReportBody): FormData => {
if (Array.isArray(body.transactions)) {
body.transactions = body.transactions.map(transaction => {
switch (typeof transaction) {
case 'object':
return transaction
case 'string':
try {
return JSON.parse(transaction) as BackendApiTransaction
} catch (error: unknown) {
return null
}
default:
return null
body.transactions = body.transactions.reduce((acc: BackendApiTransaction[], transaction) => {
let value = undefined
if (typeof transaction === 'object') {
value = transaction
} else {
value = safeFromJsonString<BackendApiTransaction>(transaction)
}
if (value) {
acc.push(value)
}
}).filter(element => element != null) as BackendApiTransaction[]
return acc
}, [])
}
return objectToFormData(body as BodyValue)
}
Expand Down
21 changes: 10 additions & 11 deletions app/javascript/src/Types/SwaggerTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,6 @@ export interface ApiDocsServices {
apis: ApiDocsService[];
}

export interface RequestData extends Request {
url: string;
method: SupportedHTTPMethods;
body: string;
credentials: string;
headers: Record<string, string>;
requestInterceptor?: ((request: Request) => Promise<Request> | Request) | undefined;
responseInterceptor?: ((response: Response) => Promise<Response> | Response) | undefined;
}

export interface ExecuteData {
contextUrl: string;
fetch: (arg: unknown) => unknown;
Expand All @@ -37,7 +27,7 @@ export interface ExecuteData {
operationId: string;
parameters: unknown;
pathName: string;
requestBody: unknown;
requestBody?: unknown;
requestContentType: string;
requestInterceptor?: ((request: Request) => Promise<Request> | Request) | undefined;
responseContentType: string;
Expand Down Expand Up @@ -66,3 +56,12 @@ export interface BackendApiReportBody {
service_id?: string;
transactions?: (BackendApiTransaction | string)[];
}

/**
* when using Record notation, the following error is thrown:
* 'TS2456: Type alias 'BodyValue' circularly references itself.'
*/
export type BodyValue = BodyValue[] | boolean | number | string | { [key: string]: BodyValue } | null | undefined
export type BodyValueObject = Record<string, BodyValue>

export type FormData = Record<string, boolean | number | string>
157 changes: 129 additions & 28 deletions spec/javascripts/ActiveDocs/ThreeScaleApiDocs.spec.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,88 @@
import { transformReportRequestBody } from 'ActiveDocs/ThreeScaleApiDocs'
import { objectToFormData, transformReportRequestBody } from 'ActiveDocs/ThreeScaleApiDocs'

import type { BackendApiReportBody } from 'Types/SwaggerTypes'
import type { BackendApiReportBody, BackendApiTransaction, BodyValue } from 'Types/SwaggerTypes'

const transaction1: BackendApiTransaction = {
app_id: 'app-id1',
timestamp: '2023-03-29 00:00:00 -01:00',
usage: {
'hit1-1': 11,
'hit1-2': 12
},
log: {
request: 'request1',
response: 'response1',
code: '200'
}
}
const transaction2: BackendApiTransaction = {
app_id: 'app-id2',
timestamp: '2023-03-29 00:00:00 -02:00',
usage: {
'hit2-1': 21,
'hit2-2': 22
},
log: {
request: 'request2',
response: 'response2',
code: '200'
}
}
const body: BackendApiReportBody = {
service_token: 'token',
service_id: '123',
transactions: [
{
app_id: 'app-id1',
timestamp: '2023-03-29 00:00:00 -01:00',
usage: {
'hit1-1': 11,
'hit1-2': 12
},
log: {
request: 'request1',
response: 'response1',
code: '200'
}
},
{
app_id: 'app-id2',
timestamp: '2023-03-29 00:00:00 -02:00',
usage: {
'hit2-1': 21,
'hit2-2': 22
},
log: {
request: 'request2',
response: 'response2',
code: '200'
}
}
transaction1,
transaction2
]
}

describe('objectToFormData', () => {
it('transforms the object to form data', () => {
const object: BodyValue = {
number: 1,
string: 'string with whitespace',
object: {
numField: 2,
strField: 'str',
boolField: true
},
array: [
{
foo: 'bar'
},
{
foo: 'baz'
}
],
nullField: null,
undefinedField: undefined,
emptyField: ''
}

expect(objectToFormData(object)).toEqual({
number: 1,
string: 'string with whitespace',
'object[numField]': 2,
'object[strField]': 'str',
'object[boolField]': true,
'array[0][foo]': 'bar',
'array[1][foo]': 'baz',
nullField: '',
undefinedField: '',
emptyField: ''
})
})

it('returns an empty object if argument is not a valid object', () => {
expect(objectToFormData('hello')).toEqual({})
expect(objectToFormData(true)).toEqual({})
expect(objectToFormData(123)).toEqual({})
expect(objectToFormData(['q', 'w', 'r'])).toEqual({})
})

})

describe('transformReportRequestBody', () => {
it('transforms the transactions array when transaction is an object', () => {
const result = transformReportRequestBody(body)
Expand All @@ -58,4 +106,57 @@ describe('transformReportRequestBody', () => {
'transactions[1][log][code]': '200'
})
})

it('transforms the transactions array when a transaction is a serialized JSON', () => {
const bodyWithSerializedTransaction = {
...body,
transactions: [
transaction1,
'{\n "app_id": "app-id2",\n "timestamp": "2023-03-29 00:00:00 -02:00",\n "usage": {\n "hit2-1": 21,\n "hit2-2": 22\n },\n "log": {\n "request": "request2",\n "response": "response2",\n "code": "200"\n }\n}']
}
const result = transformReportRequestBody(bodyWithSerializedTransaction)

expect(result).toEqual({
service_token: 'token',
service_id: '123',
'transactions[0][app_id]': 'app-id1',
'transactions[0][timestamp]': '2023-03-29 00:00:00 -01:00',
'transactions[0][usage][hit1-1]': 11,
'transactions[0][usage][hit1-2]': 12,
'transactions[0][log][request]': 'request1',
'transactions[0][log][response]': 'response1',
'transactions[0][log][code]': '200',
'transactions[1][app_id]': 'app-id2',
'transactions[1][timestamp]': '2023-03-29 00:00:00 -02:00',
'transactions[1][usage][hit2-1]': 21,
'transactions[1][usage][hit2-2]': 22,
'transactions[1][log][request]': 'request2',
'transactions[1][log][response]': 'response2',
'transactions[1][log][code]': '200'
})
})

it('skips the transactions with invalid format', () => {
const bodyWithInvalidTransactions = {
...body,
transactions: [transaction1, 'invalid', 'anotherOne']
}
const consoleErrorMock = jest.spyOn(console, 'error').mockImplementation()

const result = transformReportRequestBody(bodyWithInvalidTransactions)
expect(result).toEqual({
service_token: 'token',
service_id: '123',
'transactions[0][app_id]': 'app-id1',
'transactions[0][timestamp]': '2023-03-29 00:00:00 -01:00',
'transactions[0][usage][hit1-1]': 11,
'transactions[0][usage][hit1-2]': 12,
'transactions[0][log][request]': 'request1',
'transactions[0][log][response]': 'response1',
'transactions[0][log][code]': '200'
})

expect(consoleErrorMock).toHaveBeenCalledTimes(2)
consoleErrorMock.mockRestore()
})
})
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module.exports = {
execute: jest.fn()
execute: jest.fn()
}

0 comments on commit 986609e

Please sign in to comment.