nodejs/node · error · InvalidArgumentError

UND_ERR_INVALID_ARG

UND_ERR_INVALID_ARG

Error message

options.operator must to be a case insensitive string equal to 'OR' or 'AND'

What it means

Thrown by the mock call-history filter logic (`InvalidArgumentError`, code `UND_ERR_INVALID_ARG`) when `options.operator` is present in a `filterCalls` criteria-options object but is not a case-insensitive `'OR'` or `'AND'`. The operator controls how multiple criteria keys (protocol, host, path, method, etc.) are combined: `OR` returns the union (default), `AND` returns only logs matching every criterion. Validation runs in `buildAndValidateFilterCallsOptions` before filtering. There is a second, near-identical throw inside `handleFilterCallsWithOptions` that exists only as a defensive guard and should be unreachable if validation ran.

Source

Thrown at deps/undici/src/lib/mock/mock-call-history.js:25

  switch (options.operator) {
    case 'OR':
      store.push(...handler(criteria, allLogs))

      return store
    case 'AND':
      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\'')
  }
}

function buildAndValidateFilterCallsOptions (options = {}) {
  const finalOptions = {}

  if ('operator' in options) {
    if (typeof options.operator !== 'string' || (options.operator.toUpperCase() !== 'OR' && options.operator.toUpperCase() !== 'AND')) {
      throw new InvalidArgumentError('options.operator must to be a case insensitive string equal to \'OR\' or \'AND\'')
    }

    return {
      ...finalOptions,
      operator: options.operator.toUpperCase()
    }
  }

  return finalOptions
}

function makeFilterCalls (parameterName) {
  return (parameterValue, logs = this.logs) => {
    if (typeof parameterValue === 'string' || parameterValue == null) {
      return logs.filter((log) => {
        return log[parameterName] === parameterValue
      })
    }

View on GitHub (pinned to 1b2de5e052)

Solutions

  1. Use `operator: 'OR'` (default, union semantics) or `operator: 'AND'` (intersection semantics), case-insensitive.
  2. Omit the option entirely to get the default `OR` behavior.
  3. If you need richer boolean logic, post-filter the returned logs in your own code.

Example fix

// before
history.filterCalls({ path: '/x', method: 'GET' }, { operator: 'XOR' })
history.filterCalls({ path: '/x' }, { operator: true })

// after
history.filterCalls({ path: '/x', method: 'GET' }, { operator: 'AND' })
history.filterCalls({ path: '/x' }, { operator: 'OR' })
history.filterCalls({ path: '/x' }) // default OR
Defensive patterns

Strategy: validation

Validate before calling

function validateOperator(operator) {
  if (operator == null) return 'OR'
  if (typeof operator !== 'string') throw new Error('operator must be a string')
  const up = operator.toUpperCase()
  if (up !== 'OR' && up !== 'AND') throw new Error('operator must be OR or AND')
  return up
}

Type guard

function isFilterOperator(v) {
  return typeof v === 'string' && ['OR', 'AND'].includes(v.toUpperCase())
}

Prevention

When it happens

Trigger: Calling `callHistory.filterCalls(criteria, { operator: 'XOR' })`, `{ operator: 'or' }` is fine (case-insensitive), but `{ operator: 'NOT' }`, `{ operator: true }`, or `{ operator: '' }` throws. The guard is `typeof options.operator !== 'string' || (toUpperCase !== 'OR' && toUpperCase !== 'AND')`.

Common situations: Assuming additional boolean operators are supported; passing a lowercased value works but `operator: 'AND'`/`'OR'` only; passing a non-string from config; reaching the unreachable guard via a code path that skips validation (internal misuse).

Related errors


AI-assisted analysis of nodejs/node@1b2de5e052 (2026-08-13). Data as JSON: /api/errors/36a602a762e9c6eb. Report an issue: GitHub.