"""Utils for integration API tests.""" def assert_status(response, expected): """Assert ``response`` has ``expected`` status, surfacing status/reason/URL (and the body, which carries the API's error detail) on failure.""" assert response.status_code == expected, \ "\nExpected: {}\nStatus: {}\nReason: {}\nURL: {}\nBody: {}".format( expected, response.status_code, response.reason, response.url, response.text) return response def assert_ok(response): """Assert a 200 response, surfacing status, reason and URL on failure.""" return assert_status(response, 200) def is_int(value): """True if ``value`` (str or number) parses as a base-10 integer.""" try: int(value) except (TypeError, ValueError): return False return True def assert_int_keyed_map(body, context="", require_non_empty_values=True): """Assert ``body`` is a non-empty dict whose keys are all integer-parseable. Unlike ``all(int(k) for k in body)`` this treats a ``"0"`` key as valid (that expression is falsy for zero) and yields a useful message naming the offending key rather than raising a bare ``ValueError``. When ``require_non_empty_values`` is set (the default), every value must also be a non-empty string — preserving the value-side check the old inline ``all(str(x) for x in values())`` assertions performed, so a regression returning a valid key with an empty/garbage value is still caught. """ where = " for " + context if context else "" assert isinstance(body, dict), "expected a dict{}, got {}".format( where, type(body).__name__) assert body, "expected a non-empty map{}".format(where) bad_keys = [k for k in body if not is_int(k)] assert not bad_keys, "non-integer keys{}: {}".format( " in " + context if context else "", bad_keys) if require_non_empty_values: bad_values = {k: v for k, v in body.items() if not isinstance(v, str) or not v.strip()} assert not bad_values, "expected non-empty string values{}: {}".format( " in " + context if context else "", bad_values) return body