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

Option for enhanced debugging output during testing #561

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, we are weird, but revert this. And check in the changes of yarn/cache folder :)

Otherwise CI breaks, it wants the cached entries (faster CI).

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"
}
Comment on lines +272 to +275
Copy link
Collaborator

@klippx klippx Nov 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would this work?

https://docs.npmjs.com/cli/v7/configuring-npm/package-json#peerdependenciesmeta

Suggested change
"dependencies": {
"diff": "^7.0.0",
"tty-table": "^4.2.3"
}
"peerDependencies": {
"diff": "^7.0.0",
"tty-table": "^4.2.3"
},
"peerDependenciesMeta": {
"diff": {
"optional": true
},
"tty-table": {
"optional": true
}
}

I guess since this PR aims to be in "experimentation" mode we could consider have it like this for now even though it is a wonkier install experience for the user.

I am not proposing this change, just asking if you have considered it and how you feel about it.

}
118 changes: 96 additions & 22 deletions src/mocks/mock-request.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,29 @@ 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 } from './mock-utils'

/**
* @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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe interpolate a name from the id, something like:

Suggested change
this.mockName = props.mockName ? props.mockName : this.id
this.mockName = props.mockName ? props.mockName : `Mock#${this.id}`

this.method = props.method || 'get'
this.urlFunction = typeof props.url === 'function'
this.url = props.url
Expand Down Expand Up @@ -80,33 +87,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,
requestValue: 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,
requestValue: 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