def matches(self, recorded: RecordedHTTPRequest) -> Union[bool, MismatchReason]: """ Test if an http request meets the expectations of self Args: recorded: a recorded http request Returns: True if the request matches, or a MismatchReason object with the reason why otherwise. """ reasons = list(self.scope_mismatch_reasons(recorded)) if self.method and self.method != recorded.method: reasons.append(reason_is_ne('body', self.method, recorded.method)) if self.body_decode is not None: try: body = self.body_decode(recorded.content) except Exception as e: reasons.append(f'failed to parse content: {e!r}') else: if self.data != body: reasons.append(reason_is_ne('content', self.data, body)) if reasons: return MismatchReason(', '.join(reasons)) return True
def matches(self, message: RecordedWSMessage) -> Union[bool, MismatchReason]: """ Checks whether a recorded message matches the expectation Args: message: the recorded message Returns: True if the message matches, or a mismatch reason otherwise. """ if self.sender != message.sender: return MismatchReason( reason_is_ne('sender', self.sender, message.sender)) if self.data is ...: return True elif isinstance(self.data, Pattern): try: match = self.data.fullmatch( message.data) # type:ignore[arg-type] except TypeError: # this would happen when we try to use byte patterns on strs or vice-versa match = None if not match: return MismatchReason( f'expected data to match pattern {self.data.pattern!r}, got {message.data!r}' ) return True else: if self.data != message.data: return MismatchReason( reason_is_ne('data', self.data, message.data)) return True
def matches(self, recorded: RecordedWSTranscript): """ Checks whether a recorded transcript matches the expectation Args: recorded: the recorded transcript Returns: True if the transcript matches, or a mismatch reason otherwise. """ if recorded.close is None: raise RuntimeError('the transcript is not yet done') reasons = list(self.scope_mismatch_reasons(recorded)) if self.close is not None and recorded.close != self.close: reasons.append( reason_is_ne('close_code', self.close, recorded.close)) if self.accepted is not None and recorded.accepted != self.accepted: reasons.append( reason_is_ne('accepted', self.accepted, recorded.accepted)) if not self.any_start and not self.any_end and len(recorded) != len( self.expected_messages): reasons.append( f'expected exactly {len(self.expected_messages)} messages, found {len(recorded)}' ) if len(recorded) < len(self.expected_messages): reasons.append( f'expected at least {len(self.expected_messages)} messages, found only {len(recorded)}' ) if reasons: return MismatchReason(', '.join(reasons)) # in order to account for any_start, any_end, we check every subsequence of the recorded transcript # we store a list of candidate indices for the subsequence start if not self.any_start: # the case of (not any_start and not any_end) is covered by the first condition indices: Iterable[int] = (0, ) elif not self.any_end: indices = (len(recorded) - len(self.expected_messages), ) else: indices = range(0, len(recorded) - len(self.expected_messages) + 1) whynots = [] for start_index in indices: for i, expected_message in enumerate(self.expected_messages): rec = recorded[start_index + i] match = expected_message.matches(rec) if not match: whynots.append( f'{rec} did not match {expected_message}: {match}') break else: return True return MismatchReason('could not find expected calls' + ''.join('\n\t' + wn for wn in whynots))
def scope_mismatch_reasons(self, recorded) -> Iterator[str]: header_match = _matches_expected_multimap('header', self.headers, recorded.headers) if header_match: yield header_match if (self.path_pattern is not None and self.path_pattern.fullmatch(str(recorded.path)) is None): yield reason_is_ne('path', self.path_pattern.pattern, recorded.path) path_params_match = _matches_expected_map('path_params', self.path_params, recorded.path_params) if path_params_match: yield path_params_match query_params_match = _matches_expected_multimap( 'query_params', self.query_params, recorded.query_params) if query_params_match: yield query_params_match
def _matches_expected_map(name: str, expected: Optional[Tuple[Mapping[K, V], bool]], recorded: Mapping[K, V]) \ -> Optional[str]: """ Matches a map against the return value of _to_expected_map Args: name: the name of the field being matched expected: the expected value, as returned by _to_expected_map recorded: the recorded value to match Returns: True if the condition matches, or a MismatchReason otherwise. """ if expected is None: return None expected_value, is_subset = expected if is_subset: submap_match = _is_submap_of(expected_value, recorded) if not submap_match: return f'{name} mismatch: {submap_match}' else: if expected_value != recorded: return reason_is_ne(name, expected_value, recorded) return None