Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Olof Dahlbom committed Sep 19, 2024
1 parent 8d1835e commit b64f4bb
Show file tree
Hide file tree
Showing 12 changed files with 1,348 additions and 30 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ tsconfig.tsbuildinfo
tmp/
dist/
.vscode/
.yarn/cache
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@
"@babel/register": "^7.24.6",
"@changesets/cli": "^2.27.8",
"@chiragrupani/karma-chromium-edge-launcher": "^2.4.1",
"@types/diff": "^5.2.2",
"@types/jest": "^29.5.12",
"@types/karma": "^6.3.8",
"@types/node": "^22.5.4",
Expand Down Expand Up @@ -267,5 +268,9 @@
"webpack": "^5.94.0",
"whatwg-fetch": "^3.6.20"
},
"packageManager": "[email protected]"
"packageManager": "[email protected]",
"dependencies": {
"diff": "^7.0.0",
"tty-table": "^4.2.3"
}
}
119 changes: 97 additions & 22 deletions src/mocks/mock-request.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,30 @@ import MockAssert from './mock-assert'
import Response from '../response'
import { isPlainObject } from '../utils/index'
import { clone } from '../utils/clone'
import { sortedUrl, toSortedQueryString, isSubset } from './mock-utils'
import { sortedUrl, toSortedQueryString, filterKeys, diffString } from './mock-utils'
import { mock } from 'node:test'

/**
* @param {number} id
* @param {object} props
* @param {string} props.method
* @param {string|function} props.url
* @param {string|function} props.body - request body
* @param {string} props.mockName
* @param {object} props.response
* @param {string} props.response.body
* @param {object} props.response.headers
* @param {integer} props.response.status
*/

const MATCHED_AS_UNDEFINED_IN_MOCK = 'MATCHED_AS_UNDEFINED_IN_MOCK'
const MATCHED_BY_FUNCTION = 'MATCHED_BY_FUNCTION'
const MISMATCH_BY_FUNCTION = 'MISMATCHED_BY_FUNCTION'

function MockRequest(id, props) {
this.id = id

this.mockName = props.mockName ? props.mockName : this.id
this.method = props.method || 'get'
this.urlFunction = typeof props.url === 'function'
this.url = props.url
Expand Down Expand Up @@ -80,33 +88,100 @@ MockRequest.prototype = {
return new MockAssert(this.calls)
},

/**
* Checks if the request matches with the mock HTTP method, URL, headers and body
*
* @return {boolean}
*/
isExactMatch(request) {
const bodyMatch = () => {
if (this.body === undefined) {
return true
bodyMatchRequest(request) {
if (this.body === undefined) {
return {
match: true,
mockValue: MATCHED_AS_UNDEFINED_IN_MOCK,
value: MATCHED_AS_UNDEFINED_IN_MOCK,
}
}
if (this.bodyFunction) {
const match = this.body(request.body())
const value = match ? MATCHED_BY_FUNCTION : MISMATCH_BY_FUNCTION
return { match, mockValue: value, requestValue: value }
}
const requestBodyAsString = toSortedQueryString(request.body())
const match = this.body === requestBodyAsString
return {
match,
mockValue: decodeURIComponent(this.body),
requestValue: decodeURIComponent(requestBodyAsString),
}
},

return this.bodyFunction
? this.body(request.body())
: this.body === toSortedQueryString(request.body())
urlMatchRequest(request) {
if (this.urlFunction) {
const match = Boolean(this.url(request.url(), request.params()))
const value = match ? MATCHED_BY_FUNCTION : MISMATCH_BY_FUNCTION
return { match, mockValue: value, requestValue: value }
}
const requestUrlAsSortedString = sortedUrl(request.url())
const mockRequestUrlAsSortedString = sortedUrl(this.url)
const match = mockRequestUrlAsSortedString === requestUrlAsSortedString
return {
match,
mockValue: decodeURIComponent(mockRequestUrlAsSortedString),
requestValue: decodeURIComponent(requestUrlAsSortedString),
}
},

const urlMatch = this.urlFunction
? this.url(request.url(), request.params())
: sortedUrl(this.url) === sortedUrl(request.url())
headersMatchRequest(request) {
if (!this.headers)
return {
match: true,
mockValue: MATCHED_AS_UNDEFINED_IN_MOCK,
value: MATCHED_AS_UNDEFINED_IN_MOCK,
}
if (this.headersFunction) {
const match = this.headers(request.headers())
const value = match ? MATCHED_BY_FUNCTION : MISMATCH_BY_FUNCTION
return { match, mockValue: value, requestValue: value }
}
const filteredRequestHeaders = filterKeys(this.headersObject, request.headers())
const requestHeadersAsSortedString = toSortedQueryString(filteredRequestHeaders)
const mockRequestHeadersAsSortedString = toSortedQueryString(this.headersObject)
const match = requestHeadersAsSortedString === mockRequestHeadersAsSortedString

return {
match,
mockValue: mockRequestHeadersAsSortedString,
requestValue: requestHeadersAsSortedString,
}
},

const headerMatch =
!this.headers ||
(this.headersFunction
? this.headers(request.headers())
: isSubset(this.headersObject, request.headers()))
methodMatchRequest(request) {
const requestMethod = request.method()
const match = this.method === requestMethod
return {
match,
mockValue: this.method,
requestValue: requestMethod,
}
},

return this.method === request.method() && urlMatch && bodyMatch() && headerMatch
getRequestMatching(request) {
const method = this.methodMatchRequest(request)
const url = this.urlMatchRequest(request)
const body = this.bodyMatchRequest(request)
const headers = this.headersMatchRequest(request)
return {
mockName: this.mockName,
isExactMatch: method.match && url.match && body.match && headers.match,
isPartialMatch: this.isPartialMatch(request),
method,
url,
body,
headers,
}
},
/**
* Checks if the request matches with the mock HTTP method, URL, headers and body
*
* @return {boolean}
*/
isExactMatch(request) {
return this.getRequestMatching(request).isExactMatch
},

/**
Expand Down
28 changes: 28 additions & 0 deletions src/mocks/mock-resource.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ function MockResource(id, client) {
this.id = id
this.manifest = client._manifest
this.resourceName = null
this.mockName = null
this.methodName = null
this.requestParams = {}
this.responseData = null
Expand All @@ -37,6 +38,32 @@ MockResource.prototype = {
return this
},

/**
* Names you mock instance for debugging purposes.
* @return {MockResource}
*/
named(mockName) {
this.mockName = mockName
return this
},

/**
* Creates a name for the mock based on the client id and the resource name.
* @returns {String}
*/
getName() {
const { mockName, manifest, resourceName, id } = this
const { clientId } = manifest || {}
if (mockName) return mockName

const resourcePart = resourceName || id
if (clientId) {
return `${clientId} - ${resourcePart}`
}

return resourceName ? `${resourceName} - ${id}` : id
},

/**
* @return {MockResource}
*/
Expand Down Expand Up @@ -115,6 +142,7 @@ MockResource.prototype = {

if (!this.mockRequest) {
this.mockRequest = new MockRequest(this.id, {
mockName: this.getName(),
method: finalRequest.method(),
url: this.generateUrlMatcher(finalRequest),
body: finalRequest.body(),
Expand Down
7 changes: 7 additions & 0 deletions src/mocks/mock-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ export function isSubset(A, B) {
return toSortedQueryString(A) === toSortedQueryString(filteredB)
}

export function filterKeys(A, B) {
// Make B only contain the non-nullish keys it has in in common with A
const keysFromA = validKeys(A)
const filteredB = filterByPredicate(B, (keyFromB) => keysFromA.includes(keyFromB))
return filteredB
}

/**
* Sort the query params on a URL based on the 'key=value' string value.
* E.g. /example?b=2&a=1 will become /example?a=1&b=2
Expand Down
Loading

0 comments on commit b64f4bb

Please sign in to comment.