diff --git a/tests/unit/test_cassettes.py b/tests/unit/test_cassettes.py index 4541530..01d8c70 100644 --- a/tests/unit/test_cassettes.py +++ b/tests/unit/test_cassettes.py @@ -317,3 +317,51 @@ def test_use_as_decorator_on_generator(): yield 2 assert list(test_function()) == [1, 2] + + +@mock.patch("vcr.cassette.get_matchers_results") +def test_find_requests_with_most_matches_one_similar_request(mock_get_matchers_results): + mock_get_matchers_results.side_effect = [ + (["method"], [("path", "failed : path"), ("query", "failed : query")]), + (["method", "path"], [("query", "failed : query")]), + ([], [("method", "failed : method"), ("path", "failed : path"), ("query", "failed : query")]), + ] + + cassette = Cassette("test") + for request in range(1, 4): + cassette.append(request, 'response') + result = cassette.find_requests_with_most_matches("fake request") + assert result == [(2, ["method", "path"], [("query", "failed : query")])] + + +@mock.patch("vcr.cassette.get_matchers_results") +def test_find_requests_with_most_matches_no_similar_requests(mock_get_matchers_results): + mock_get_matchers_results.side_effect = [ + ([], [("path", "failed : path"), ("query", "failed : query")]), + ([], [("path", "failed : path"), ("query", "failed : query")]), + ([], [("path", "failed : path"), ("query", "failed : query")]), + ] + + cassette = Cassette("test") + for request in range(1, 4): + cassette.append(request, 'response') + result = cassette.find_requests_with_most_matches("fake request") + assert result == [] + + +@mock.patch("vcr.cassette.get_matchers_results") +def test_find_requests_with_most_matches_many_similar_requests(mock_get_matchers_results): + mock_get_matchers_results.side_effect = [ + (["method", "path"], [("query", "failed : query")]), + (["method"], [("path", "failed : path"), ("query", "failed : query")]), + (["method", "path"], [("query", "failed : query")]), + ] + + cassette = Cassette("test") + for request in range(1, 4): + cassette.append(request, 'response') + result = cassette.find_requests_with_most_matches("fake request") + assert result == [ + (1, ["method", "path"], [("query", "failed : query")]), + (3, ["method", "path"], [("query", "failed : query")]) + ] diff --git a/vcr/cassette.py b/vcr/cassette.py index 79d8816..7ba092c 100644 --- a/vcr/cassette.py +++ b/vcr/cassette.py @@ -8,7 +8,7 @@ import wrapt from .compat import contextlib from .errors import UnhandledHTTPRequestError -from .matchers import requests_match, uri, method +from .matchers import requests_match, uri, method, get_matchers_results from .patch import CassettePatcherBuilder from .serializers import yamlserializer from .persisters.filesystem import FilesystemPersister @@ -290,6 +290,38 @@ class Cassette(object): def rewind(self): self.play_counts = collections.Counter() + def find_requests_with_most_matches(self, request): + """ + Get the most similar request(s) stored in the cassette + of a given request as a list of tuples like this: + - the request object + - the successful matchers as string + - the failed matchers and the related assertion message with the difference details as strings tuple + + This is useful when a request failed to be found, + we can get the similar request(s) in order to know what have changed in the request parts. + """ + best_matches = [] + request = self._before_record_request(request) + for index, (stored_request, response) in enumerate(self.data): + successes, fails = get_matchers_results(request, stored_request, self._match_on) + best_matches.append((len(successes), stored_request, successes, fails)) + best_matches.sort(key=lambda t: t[0], reverse=True) + # Get the first best matches (multiple if equal matches) + final_best_matches = [] + previous_nb_success = best_matches[0][0] + for best_match in best_matches: + nb_success = best_match[0] + # Do not keep matches that have 0 successes, + # it means that the request is totally different from + # the ones stored in the cassette + if nb_success < 1 or previous_nb_success != nb_success: + break + previous_nb_success = nb_success + final_best_matches.append(best_match[1:]) + + return final_best_matches + def _as_dict(self): return {"requests": self.requests, "responses": self.responses}