From c3010fa3c2f5b3798fb9feb52b10e35bc59ada0e Mon Sep 17 00:00:00 2001 From: DeepView Autofix <276251120+deepview-autofix@users.noreply.github.com> Date: Thu, 16 Apr 2026 21:37:38 +0300 Subject: [PATCH] fix(mock): make filterCalls AND operator actually intersect results `makeFilterCalls` returned an arrow function whose `this` cannot be rebound by `.call()`, so `handler.call({ logs: store }, criteria)` in `handleFilterCallsWithOptions` always filtered against the full `this.logs` instead of the narrowed `store`, causing AND to behave like OR. Pass the source logs explicitly to the filter helpers and seed the store with `this.logs` for AND so each criterion narrows the previous result. Co-Authored-By: Claude Co-Authored-By: DeepView Autofix <276251120+deepview-autofix@users.noreply.github.com> Co-Authored-By: Nikita Skovoroda Signed-off-by: Nikita Skovoroda --- lib/mock/mock-call-history.js | 30 +++++++++++++++--------------- test/mock-call-history.js | 19 ++++++++++++++++++- 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/lib/mock/mock-call-history.js b/lib/mock/mock-call-history.js index d4a92b2b24b..74de68247c7 100644 --- a/lib/mock/mock-call-history.js +++ b/lib/mock/mock-call-history.js @@ -3,14 +3,14 @@ const { kMockCallHistoryAddLog } = require('./mock-symbols') const { InvalidArgumentError } = require('../core/errors') -function handleFilterCallsWithOptions (criteria, options, handler, store) { +function handleFilterCallsWithOptions (criteria, options, handler, store, allLogs) { switch (options.operator) { case 'OR': - store.push(...handler(criteria)) + store.push(...handler(criteria, allLogs)) return store case 'AND': - return handler.call({ logs: store }, criteria) + return handler(criteria, store) default: // guard -- should never happens because buildAndValidateFilterCallsOptions is called before throw new InvalidArgumentError('options.operator must to be a case insensitive string equal to \'OR\' or \'AND\'') @@ -35,14 +35,14 @@ function buildAndValidateFilterCallsOptions (options = {}) { } function makeFilterCalls (parameterName) { - return (parameterValue) => { + return (parameterValue, logs) => { if (typeof parameterValue === 'string' || parameterValue == null) { - return this.logs.filter((log) => { + return logs.filter((log) => { return log[parameterName] === parameterValue }) } if (parameterValue instanceof RegExp) { - return this.logs.filter((log) => { + return logs.filter((log) => { return parameterValue.test(log[parameterName]) }) } @@ -175,30 +175,30 @@ class MockCallHistory { const finalOptions = { operator: 'OR', ...buildAndValidateFilterCallsOptions(options) } - let maybeDuplicatedLogsFiltered = [] + let maybeDuplicatedLogsFiltered = finalOptions.operator === 'AND' ? this.logs : [] if ('protocol' in criteria) { - maybeDuplicatedLogsFiltered = handleFilterCallsWithOptions(criteria.protocol, finalOptions, this.filterCallsByProtocol, maybeDuplicatedLogsFiltered) + maybeDuplicatedLogsFiltered = handleFilterCallsWithOptions(criteria.protocol, finalOptions, this.filterCallsByProtocol, maybeDuplicatedLogsFiltered, this.logs) } if ('host' in criteria) { - maybeDuplicatedLogsFiltered = handleFilterCallsWithOptions(criteria.host, finalOptions, this.filterCallsByHost, maybeDuplicatedLogsFiltered) + maybeDuplicatedLogsFiltered = handleFilterCallsWithOptions(criteria.host, finalOptions, this.filterCallsByHost, maybeDuplicatedLogsFiltered, this.logs) } if ('port' in criteria) { - maybeDuplicatedLogsFiltered = handleFilterCallsWithOptions(criteria.port, finalOptions, this.filterCallsByPort, maybeDuplicatedLogsFiltered) + maybeDuplicatedLogsFiltered = handleFilterCallsWithOptions(criteria.port, finalOptions, this.filterCallsByPort, maybeDuplicatedLogsFiltered, this.logs) } if ('origin' in criteria) { - maybeDuplicatedLogsFiltered = handleFilterCallsWithOptions(criteria.origin, finalOptions, this.filterCallsByOrigin, maybeDuplicatedLogsFiltered) + maybeDuplicatedLogsFiltered = handleFilterCallsWithOptions(criteria.origin, finalOptions, this.filterCallsByOrigin, maybeDuplicatedLogsFiltered, this.logs) } if ('path' in criteria) { - maybeDuplicatedLogsFiltered = handleFilterCallsWithOptions(criteria.path, finalOptions, this.filterCallsByPath, maybeDuplicatedLogsFiltered) + maybeDuplicatedLogsFiltered = handleFilterCallsWithOptions(criteria.path, finalOptions, this.filterCallsByPath, maybeDuplicatedLogsFiltered, this.logs) } if ('hash' in criteria) { - maybeDuplicatedLogsFiltered = handleFilterCallsWithOptions(criteria.hash, finalOptions, this.filterCallsByHash, maybeDuplicatedLogsFiltered) + maybeDuplicatedLogsFiltered = handleFilterCallsWithOptions(criteria.hash, finalOptions, this.filterCallsByHash, maybeDuplicatedLogsFiltered, this.logs) } if ('fullUrl' in criteria) { - maybeDuplicatedLogsFiltered = handleFilterCallsWithOptions(criteria.fullUrl, finalOptions, this.filterCallsByFullUrl, maybeDuplicatedLogsFiltered) + maybeDuplicatedLogsFiltered = handleFilterCallsWithOptions(criteria.fullUrl, finalOptions, this.filterCallsByFullUrl, maybeDuplicatedLogsFiltered, this.logs) } if ('method' in criteria) { - maybeDuplicatedLogsFiltered = handleFilterCallsWithOptions(criteria.method, finalOptions, this.filterCallsByMethod, maybeDuplicatedLogsFiltered) + maybeDuplicatedLogsFiltered = handleFilterCallsWithOptions(criteria.method, finalOptions, this.filterCallsByMethod, maybeDuplicatedLogsFiltered, this.logs) } const uniqLogsFiltered = [...new Set(maybeDuplicatedLogsFiltered)] diff --git a/test/mock-call-history.js b/test/mock-call-history.js index 6e05f21f9fa..e074ddf3b5f 100644 --- a/test/mock-call-history.js +++ b/test/mock-call-history.js @@ -409,7 +409,24 @@ describe('MockCallHistory - filterCalls with options', () => { const filtered = mockCallHistoryHello.filterCalls({ path: '/', port: '4000' }, { operator: 'AND' }) - t.assert.strictEqual(filtered.length, 2) + t.assert.strictEqual(filtered.length, 1) + }) + + test('should use "AND" operator narrowing through every criterion', t => { + t.plan(2) + + const mockCallHistoryHello = new MockCallHistory('hello') + + mockCallHistoryHello[kMockCallHistoryAddLog]({ path: '/', origin: 'http://localhost:4000', method: 'GET' }) + mockCallHistoryHello[kMockCallHistoryAddLog]({ path: '/', origin: 'http://localhost:4000', method: 'POST' }) + mockCallHistoryHello[kMockCallHistoryAddLog]({ path: '/', origin: 'http://localhost:5000', method: 'GET' }) + mockCallHistoryHello[kMockCallHistoryAddLog]({ path: '/foo', origin: 'http://localhost:4000', method: 'GET' }) + + const andFiltered = mockCallHistoryHello.filterCalls({ path: '/', port: '4000', method: 'GET' }, { operator: 'AND' }) + t.assert.strictEqual(andFiltered.length, 1) + + const orFiltered = mockCallHistoryHello.filterCalls({ path: '/', port: '4000', method: 'GET' }, { operator: 'OR' }) + t.assert.strictEqual(orFiltered.length, 4) }) test('should use "AND" operator with a lot of filters', t => {