# -*- coding: utf-8 -*- import json import logging import os from urllib.parse import urljoin import allure import requests from joyhub_backend.library.auth import JoyhubAuth, TIMEOUT MANAGER_BASE_URL = os.getenv( "JOYHUB_MANAGER_BASE_URL", "http://test-manager-api.best-envision.com", ) class JoyhubInterface(object): def __init__(self): self.auth = JoyhubAuth() self.session = requests.Session() self.base_url = MANAGER_BASE_URL.rstrip("/") + "/" def request(self, case_name, method, path, body=None, query=None, headers=None, expected_code=0): url = path if path.startswith("http") else urljoin(self.base_url, path.lstrip("/")) request_headers = self.auth.auth_headers() if headers: request_headers.update({key: value for key, value in headers.items() if key.lower() != "authorization"}) with allure.step("操作步骤:{}".format(case_name)): self._attach_request(url, method, request_headers, body, query) logging.info("case: %s", case_name) logging.info("request url: %s", url) logging.info("request method: %s", method) logging.info("request headers: %s", request_headers) logging.info("request body: %s", body) logging.info("request query: %s", query) request_kwargs = { "method": method, "url": url, "params": query, "headers": request_headers, "timeout": TIMEOUT, } if method.upper() in ("POST", "PUT", "PATCH"): request_kwargs["json"] = body elif body: request_kwargs["params"] = dict(query or {}, **body) if isinstance(body, dict) else query response = self.session.request(**request_kwargs) self._attach_response(response) self._attach_log(case_name, url, method, request_headers, body, query, response) logging.info("response status: %s", response.status_code) logging.info("response body: %s", response.text) is_business_api = self._is_business_api(url) assertion_text = "HTTP状态码为200,且业务code为{}".format(expected_code) if is_business_api else "HTTP状态码为2xx,兼容非JSON/SSE响应" allure.attach(assertion_text, "断言内容", allure.attachment_type.TEXT) with allure.step("断言内容:{}".format(assertion_text)): try: if is_business_api: assert response.status_code == 200, "HTTP状态码期望200,实际{},响应{}".format( response.status_code, response.text ) data = self._safe_json(response) assert isinstance(data, dict), "业务接口响应不是JSON对象,响应{}".format(response.text) assert "code" in data, "响应缺少 code 字段,响应{}".format(data) assert data.get("code") == expected_code, "业务code期望{},实际{},msg={},响应{}".format( expected_code, data.get("code"), data.get("msg"), data ) else: assert 200 <= response.status_code < 300, "HTTP状态码期望2xx,实际{},响应{}".format( response.status_code, response.text ) data = self._non_json_result(response) allure.attach("无", "断言失败原因", allure.attachment_type.TEXT) return data except AssertionError as error: allure.attach(str(error), "断言失败原因", allure.attachment_type.TEXT) raise AssertionError("断言失败原因:{}".format(error)) @staticmethod def _attach_request(url, method, headers, body, query): allure.attach(str(url), "请求url", allure.attachment_type.TEXT) allure.attach(str(method), "请求方式", allure.attachment_type.TEXT) allure.attach(json.dumps(headers, ensure_ascii=False, indent=2), "请求头", allure.attachment_type.JSON) allure.attach(json.dumps(query or {}, ensure_ascii=False, indent=2), "请求query", allure.attachment_type.JSON) allure.attach(json.dumps(body or {}, ensure_ascii=False, indent=2), "请求体", allure.attachment_type.JSON) @staticmethod def _attach_response(response): allure.attach(str(response.status_code), "响应状态码", allure.attachment_type.TEXT) content_type = response.headers.get("Content-Type", "") attachment_type = allure.attachment_type.JSON if "json" in content_type.lower() else allure.attachment_type.TEXT allure.attach(response.text, "响应体/日志", attachment_type) def _is_business_api(self, url): return url.startswith(self.base_url) @staticmethod def _safe_json(response): if not response.text.strip(): return None return response.json() @staticmethod def _non_json_result(response): try: data = response.json() except ValueError: data = { "status_code": response.status_code, "content_type": response.headers.get("Content-Type", ""), "text": response.text, } return data @staticmethod def _attach_log(case_name, url, method, headers, body, query, response): log_text = "\n".join([ "case: {}".format(case_name), "request url: {}".format(url), "request method: {}".format(method), "request headers: {}".format(headers), "request query: {}".format(query or {}), "request body: {}".format(body or {}), "response status: {}".format(response.status_code), "response body: {}".format(response.text), ]) allure.attach(log_text, "日志", allure.attachment_type.TEXT)