From f3571644e344f1f7d8b918de4c3a1bd5d44caf52 Mon Sep 17 00:00:00 2001 From: wklken Date: Mon, 6 Jul 2026 18:16:35 +0800 Subject: [PATCH] fix(apigw-manager): parse v2 api responses --- sdks/apigw-manager/CHANGE.md | 7 ++ sdks/apigw-manager/pyproject.toml | 2 +- .../commands/apply_apigw_permissions.py | 1 + .../create_version_and_release_apigw.py | 4 +- .../commands/sync_apigw_stage_mcp_servers.py | 2 +- .../src/apigw_manager/core/fetch.py | 7 +- .../src/apigw_manager/core/handler.py | 72 +++++--------- .../src/apigw_manager/core/permission.py | 5 +- .../src/apigw_manager/core/release.py | 7 +- .../src/apigw_manager/core/sync.py | 14 +-- .../src/apigw_manager/core/utils.py | 9 -- .../commands/test_apply_apigw_permissions.py | 5 +- .../test_create_version_and_release_apigw.py | 37 ++++--- .../test_sync_apigw_stage_mcp_servers.py | 70 +++++++++++++ .../tests/apigw_manager/core/test_hander.py | 98 +++++++++++-------- .../tests/apigw_manager/core/test_utils.py | 8 +- 16 files changed, 208 insertions(+), 140 deletions(-) create mode 100644 sdks/apigw-manager/tests/apigw_manager/apigw/management/commands/test_sync_apigw_stage_mcp_servers.py diff --git a/sdks/apigw-manager/CHANGE.md b/sdks/apigw-manager/CHANGE.md index a3fca0b5..37170639 100644 --- a/sdks/apigw-manager/CHANGE.md +++ b/sdks/apigw-manager/CHANGE.md @@ -1,5 +1,12 @@ ## Change logs +### 5.0.1 + +- [fix] 修复 v2 API 响应解析逻辑,20x 响应直接返回 `data`,非 20x 响应从 `error` 中解析错误信息 +- [fix] 适配 v2 资源版本接口返回结构,资源版本列表使用数组判断是否存在,发布时不再依赖资源版本响应中的 `comment` +- [fix] 修复同步环境 MCP Server 时重复读取 `data` 导致同步结果无法输出的问题 +- [fix] `apply_apigw_permissions` 申请权限时补充 v2 接口必填的 `applicant` 字段 + ### 5.0.0 - [feat] generate_resources_yaml 命令支持合并手动配置的额外资源文件 diff --git a/sdks/apigw-manager/pyproject.toml b/sdks/apigw-manager/pyproject.toml index a2ef7dfb..7ce2b318 100644 --- a/sdks/apigw-manager/pyproject.toml +++ b/sdks/apigw-manager/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "apigw-manager" -version = "5.0.0" +version = "5.0.1" description = "The SDK for managing blueking gateway resource." readme = "README.md" authors = ["blueking "] diff --git a/sdks/apigw-manager/src/apigw_manager/apigw/management/commands/apply_apigw_permissions.py b/sdks/apigw-manager/src/apigw_manager/apigw/management/commands/apply_apigw_permissions.py index 129967bc..b87c747d 100644 --- a/sdks/apigw-manager/src/apigw_manager/apigw/management/commands/apply_apigw_permissions.py +++ b/sdks/apigw-manager/src/apigw_manager/apigw/management/commands/apply_apigw_permissions.py @@ -30,6 +30,7 @@ def do(self, manager, definition, *args, **kwargs): for permission in definition: permission.setdefault("target_app_code", manager.config.bk_app_code) + permission.setdefault("applicant", permission["target_app_code"]) # v2 使用 gateway_name 替代 api_name if "api_name" in permission: diff --git a/sdks/apigw-manager/src/apigw_manager/apigw/management/commands/create_version_and_release_apigw.py b/sdks/apigw-manager/src/apigw_manager/apigw/management/commands/create_version_and_release_apigw.py index dc1c878b..b56caca3 100644 --- a/sdks/apigw-manager/src/apigw_manager/apigw/management/commands/create_version_and_release_apigw.py +++ b/sdks/apigw-manager/src/apigw_manager/apigw/management/commands/create_version_and_release_apigw.py @@ -88,7 +88,7 @@ def _create_resource_version(self, releaser, version, comment): def _check_resource_version_exists(self, fetcher, version): resource_versions = fetcher.list_resource_versions(version=str(version)) - return resource_versions["count"] != 0 + return bool(resource_versions) def _generate_sdks(self, releaser, version, *args, **kwargs): try: @@ -132,7 +132,7 @@ def handle(self, stage, title, comment, generate_sdks, no_pub, *args, **kwargs): if not no_pub: result = releaser.release( version=resource_version["version"], - comment=comment or resource_version.get("comment", ""), + comment=comment or "", stage_names=stage, ) print( diff --git a/sdks/apigw-manager/src/apigw_manager/apigw/management/commands/sync_apigw_stage_mcp_servers.py b/sdks/apigw-manager/src/apigw_manager/apigw/management/commands/sync_apigw_stage_mcp_servers.py index 56dc58b8..de25ca37 100644 --- a/sdks/apigw-manager/src/apigw_manager/apigw/management/commands/sync_apigw_stage_mcp_servers.py +++ b/sdks/apigw-manager/src/apigw_manager/apigw/management/commands/sync_apigw_stage_mcp_servers.py @@ -34,7 +34,7 @@ def get_definition(self, define, file, namespace, **kwargs): def do(self, manager, definition, *args, **kwargs): for stage_definition in definition: result = manager.sync_stage_mcp_servers(**stage_definition) - for mcp_sync_result in result.get("data", []): + for mcp_sync_result in result: print( "API gateway stage mcp servers synchronization completed [ id:%s,name:%s,action:%s ]" % (mcp_sync_result["id"], mcp_sync_result["name"], mcp_sync_result["action"]) diff --git a/sdks/apigw-manager/src/apigw_manager/core/fetch.py b/sdks/apigw-manager/src/apigw_manager/core/fetch.py index 9b98c1aa..5f41ab01 100644 --- a/sdks/apigw-manager/src/apigw_manager/core/fetch.py +++ b/sdks/apigw-manager/src/apigw_manager/core/fetch.py @@ -16,7 +16,6 @@ # to the current version of the project delivered to anyone in the future. from apigw_manager.core.handler import Handler -from apigw_manager.core.utils import itemgetter class Fetcher(Handler): @@ -25,13 +24,13 @@ class Fetcher(Handler): def public_key(self, *args, **kwargs): """Get the API gateway public key according to the name""" result = self._call_v2_with_cache(self.client.api.v2_sync_get_gateway_public_key_new, *args, **kwargs) - return self._parse_result(result, itemgetter("data")) + return self._parse_v2_result(result) def latest_resource_version(self, *args, **kwargs): """Get the latest resource version""" result = self._call_v2(self.client.api.v2_sync_get_latest_resource_version, *args, **kwargs) - return self._parse_result(result, itemgetter("data")) + return self._parse_v2_result(result) def list_resource_versions(self, *args, **kwargs): result = self._call_v2(self.client.api.v2_sync_list_resource_versions, *args, **kwargs) - return self._parse_result(result, itemgetter("data")) + return self._parse_v2_result(result) diff --git a/sdks/apigw-manager/src/apigw_manager/core/handler.py b/sdks/apigw-manager/src/apigw_manager/core/handler.py index da8ac304..ffaae513 100644 --- a/sdks/apigw-manager/src/apigw_manager/core/handler.py +++ b/sdks/apigw-manager/src/apigw_manager/core/handler.py @@ -65,24 +65,6 @@ def _put_into_cache(self, operation_id, data, result): return False - def _call_with_cache(self, operation, **kwargs): - """Call the API instance, allow data to be retrieved from the cache""" - cache_key = { - "gateway_name": kwargs.get("gateway_name", self.config.gateway_name), - "kwargs": kwargs, - } - - operation_id = operation.name - cached, result = self._get_from_cache(operation_id, cache_key) - if cached: - return result - - result = self._call(operation, **kwargs) - - self._put_into_cache(operation_id, cache_key, result) - - return result - def _call_v2_with_cache(self, operation, **kwargs): """Call the API instance (v2), allow data to be retrieved from the cache""" cache_key = { @@ -123,31 +105,6 @@ def _get_tenant_id(self): ) return bk_app_tenant_id - def _call(self, operation, files=None, **kwargs): - """Call the API instance""" - data = { - "path_params": {"api_name": kwargs.pop("gateway_name", self.config.gateway_name)}, - "data": kwargs, - "headers": { - "X-Bkapi-Authorization": kwargs.pop("x_bkapi_authorization", self._get_bkapi_authorization()), - # the header is required by the API gateway plugin bk-tenant-validate, for global tenant app! - # so we set it to system, it would not be used in the gateway - "X-Bk-Tenant-Id": self._get_tenant_id(), - }, - "files": files, - } - - operation_id = operation.name - logger.debug("call api %s, data: %s", operation_id, data) - - try: - return operation(**data) - except ResponseError as err: - message = "%s\n%s\nResponse: %s" % (err, err.curl_command, err.response_text) - raise ApiResponseError(message) - except Exception as err: - raise ApiException(operation_id) from err - def _call_v2(self, operation, files=None, **kwargs): """Call the API instance (v2 version): - Uses "gateway_name" as the key in `path_params` instead of "api_name". @@ -180,18 +137,33 @@ def _call_v2(self, operation, files=None, **kwargs): try: return operation(**data) except ResponseError as err: + if err.response_status_code is not None and not self._is_success_status_code(err.response_status_code): + self._raise_v2_result_error(self._get_response_json(err), err.response_status_code, err.response_text) + message = "%s\n%s\nResponse: %s" % (err, err.curl_command, err.response_text) raise ApiResponseError(message) except Exception as err: raise ApiException(operation_id) from err - def _parse_result(self, result, convertor, code=0): - """Check the code and convert the result""" - logger.debug("code %s, message: %s", result.get("code"), result.get("message")) - if result.get("code") != code: + def _parse_v2_result(self, result): + """Convert the v2 API response body.""" + return result.get("data") + + def _raise_v2_result_error(self, result, status_code, response_text): + if isinstance(result, dict) and result.get("error"): + error = result["error"] raise ApiResultError( - result.get("code"), - result.get("message"), + error.get("code"), + error.get("message"), ) - return convertor(result) + raise ApiResultError(status_code, response_text) + + def _get_response_json(self, err): + try: + return err.response_json() + except (TypeError, ValueError): + return None + + def _is_success_status_code(self, status_code): + return status_code is not None and 200 <= status_code < 300 diff --git a/sdks/apigw-manager/src/apigw_manager/core/permission.py b/sdks/apigw-manager/src/apigw_manager/core/permission.py index efc3c227..5033df3c 100644 --- a/sdks/apigw-manager/src/apigw_manager/core/permission.py +++ b/sdks/apigw-manager/src/apigw_manager/core/permission.py @@ -16,7 +16,6 @@ # to the current version of the project delivered to anyone in the future. from apigw_manager.core.handler import Handler -from apigw_manager.core.utils import itemgetter class Manager(Handler): @@ -25,9 +24,9 @@ class Manager(Handler): def apply_permission(self, *args, **kwargs): """Apply for API Gateway Permissions""" result = self._call_v2(self.client.api.v2_open_apply_gateway_permission, *args, **kwargs) - return self._parse_result(result, itemgetter("data")) + return self._parse_v2_result(result) def grant_permission(self, *args, **kwargs): """Grant API gateway permissions for applications""" result = self._call_v2(self.client.api.v2_sync_grant_permission, *args, **kwargs) - return self._parse_result(result, itemgetter("data")) + return self._parse_v2_result(result) diff --git a/sdks/apigw-manager/src/apigw_manager/core/release.py b/sdks/apigw-manager/src/apigw_manager/core/release.py index 9d8e7e36..cebb16c3 100644 --- a/sdks/apigw-manager/src/apigw_manager/core/release.py +++ b/sdks/apigw-manager/src/apigw_manager/core/release.py @@ -16,7 +16,6 @@ # to the current version of the project delivered to anyone in the future. from apigw_manager.core.handler import Handler -from apigw_manager.core.utils import itemgetter class Releaser(Handler): @@ -27,17 +26,17 @@ def create_resource_version(self, *args, **kwargs): kwargs.pop("title", None) result = self._call_v2(self.client.api.v2_sync_create_resource_version, *args, **kwargs) - return self._parse_result(result, itemgetter("data")) + return self._parse_v2_result(result) def release(self, *args, **kwargs): """release a version""" kwargs.pop("title", None) result = self._call_v2(self.client.api.v2_sync_release, *args, **kwargs) - return self._parse_result(result, itemgetter("data")) + return self._parse_v2_result(result) def generate_sdks(self, *args, **kwargs): """generate sdks""" result = self._call_v2(self.client.api.v2_sync_generate_sdk, *args, **kwargs) - return self._parse_result(result, itemgetter("data")) + return self._parse_v2_result(result) diff --git a/sdks/apigw-manager/src/apigw_manager/core/sync.py b/sdks/apigw-manager/src/apigw_manager/core/sync.py index 10969a1e..227bfcb4 100644 --- a/sdks/apigw-manager/src/apigw_manager/core/sync.py +++ b/sdks/apigw-manager/src/apigw_manager/core/sync.py @@ -18,7 +18,6 @@ import yaml from apigw_manager.core.handler import Handler -from apigw_manager.core.utils import itemgetter class Synchronizer(Handler): @@ -26,27 +25,28 @@ class Synchronizer(Handler): def sync_basic_config(self, *args, **kwargs): result = self._call_v2(self.client.api.v2_sync_gateway, *args, **kwargs) - return self._parse_result(result, itemgetter("data")) + return self._parse_v2_result(result) def sync_stage_config(self, *args, **kwargs): result = self._call_v2(self.client.api.v2_sync_stages, *args, **kwargs) - return self._parse_result(result, itemgetter("data")) + return self._parse_v2_result(result) def sync_stage_mcp_servers(self, *args, **kwargs): - return self._call_v2(self.client.api.v2_sync_stage_mcp_servers, *args, **kwargs) + result = self._call_v2(self.client.api.v2_sync_stage_mcp_servers, *args, **kwargs) + return self._parse_v2_result(result) def sync_resources_config(self, content, *args, **kwargs): kwargs["content"] = yaml.dump(dict(content)) result = self._call_v2(self.client.api.v2_sync_resources, *args, **kwargs) - return self._parse_result(result, itemgetter("data")) + return self._parse_v2_result(result) def sync_resource_docs_by_archive(self, *args, **kwargs): result = self._call_v2(self.client.api.v2_sync_resource_doc, *args, **kwargs) - return self._parse_result(result, itemgetter("data")) + return self._parse_v2_result(result) def add_related_apps(self, *args, **kwargs): kwargs["related_app_codes"] = kwargs.pop("related_apps") result = self._call_v2(self.client.api.v2_sync_add_related_apps, *args, **kwargs) - return self._parse_result(result, itemgetter("data")) + return self._parse_v2_result(result) diff --git a/sdks/apigw-manager/src/apigw_manager/core/utils.py b/sdks/apigw-manager/src/apigw_manager/core/utils.py index 4c991b2a..9e543c1f 100644 --- a/sdks/apigw-manager/src/apigw_manager/core/utils.py +++ b/sdks/apigw-manager/src/apigw_manager/core/utils.py @@ -19,15 +19,6 @@ from operator import getitem -def itemgetter(*keys): - """Chaining value getter of dict""" - - def getter(r): - return get_item(r, keys) - - return getter - - def get_item(r, keys): """Get the value according to the keys""" return reduce(getitem, keys, r) diff --git a/sdks/apigw-manager/tests/apigw_manager/apigw/management/commands/test_apply_apigw_permissions.py b/sdks/apigw-manager/tests/apigw_manager/apigw/management/commands/test_apply_apigw_permissions.py index e6b2d082..b49a3fbf 100644 --- a/sdks/apigw-manager/tests/apigw_manager/apigw/management/commands/test_apply_apigw_permissions.py +++ b/sdks/apigw-manager/tests/apigw_manager/apigw/management/commands/test_apply_apigw_permissions.py @@ -33,24 +33,27 @@ def test_do(mock_manager, command, configuration): [ {"api_name": "test1"}, {"gateway_name": "test2", "grant_dimension": "resource", "resource_names": ["get_user"]}, - {"gateway_name": "test3", "grant_dimension": "api"}, + {"gateway_name": "test3", "grant_dimension": "api", "applicant": "admin"}, ], ) # v2 使用 gateway_name 替代 api_name,默认按 gateway 维度申请 mock_manager.apply_permission.assert_any_call( target_app_code=configuration.bk_app_code, + applicant=configuration.bk_app_code, gateway_name="test1", grant_dimension="gateway", ) mock_manager.apply_permission.assert_any_call( target_app_code=configuration.bk_app_code, + applicant=configuration.bk_app_code, gateway_name="test2", grant_dimension="resource", resource_names=["get_user"], ) mock_manager.apply_permission.assert_any_call( target_app_code=configuration.bk_app_code, + applicant="admin", gateway_name="test3", grant_dimension="api", ) diff --git a/sdks/apigw-manager/tests/apigw_manager/apigw/management/commands/test_create_version_and_release_apigw.py b/sdks/apigw-manager/tests/apigw_manager/apigw/management/commands/test_create_version_and_release_apigw.py index 27b19c1b..0066574c 100644 --- a/sdks/apigw-manager/tests/apigw_manager/apigw/management/commands/test_create_version_and_release_apigw.py +++ b/sdks/apigw-manager/tests/apigw_manager/apigw/management/commands/test_create_version_and_release_apigw.py @@ -198,7 +198,10 @@ def test_handle_version_not_change( definition_file.write(yaml.dump(fake_resource_version)) stage = faker.pystr() - fetcher.latest_resource_version.return_value = fake_resource_version + fetcher.latest_resource_version.return_value = { + "id": 1, + "version": fake_resource_version["version"], + } releaser.release.return_value = { "version": fake_resource_version["version"], "resource_version_name": fake_resource_version["name"], @@ -211,7 +214,7 @@ def test_handle_version_not_change( releaser.create_resource_version.assert_not_called() releaser.release.assert_called_once_with( version=fake_resource_version["version"], - comment=fake_resource_version["comment"], + comment="", stage_names=stage, ) @@ -229,7 +232,12 @@ def test_handle_version_not_change_but_dirty( definition_file.write(yaml.dump(fake_resource_version)) stage = faker.pystr() fetcher.latest_resource_version.return_value = fake_resource_version - fetcher.list_resource_versions.return_value = {"count": 1} + fetcher.list_resource_versions.return_value = [ + { + "version": fake_resource_version["version"], + "comment": fake_resource_version["comment"], + } + ] current_version = command._get_version_to_be_created(parse_version(fake_resource_version["version"]), True) @@ -239,8 +247,8 @@ def test_handle_version_not_change_but_dirty( "stage_names": [stage], } releaser.create_resource_version.return_value = { + "id": 1, "version": str(current_version), - "comment": fake_resource_version["comment"], } resource_sync_manager.is_dirty.return_value = True @@ -252,7 +260,7 @@ def test_handle_version_not_change_but_dirty( ) releaser.release.assert_called_once_with( version=str(current_version), - comment=fake_resource_version["comment"], + comment="", stage_names=stage, ) @@ -279,10 +287,12 @@ def test_handle_version_changed( latest_version = "1.0.0-alpha1" fetcher.latest_resource_version.return_value = dict(fake_resource_version, version=latest_version) - fetcher.list_resource_versions.return_value = {"count": 0} + fetcher.list_resource_versions.return_value = [] releaser.create_resource_version.return_value = dict( - fake_resource_version, + { + "id": 1, + }, version=defined_version, ) stage = faker.pystr() @@ -294,7 +304,7 @@ def test_handle_version_changed( ) releaser.release.assert_any_call( version=defined_version, - comment=fake_resource_version["comment"], + comment="", stage_names=stage, ) @@ -320,7 +330,12 @@ def test_handle_version_not_set( stage = faker.pystr() fetcher.latest_resource_version.return_value = fake_resource_version - fetcher.list_resource_versions.return_value = {"count": 1} + fetcher.list_resource_versions.return_value = [ + { + "version": "0.0.1", + "comment": fake_resource_version["comment"], + } + ] defined_version = command._get_version_to_be_created(parse_version("0.0.1"), True) @@ -331,8 +346,8 @@ def test_handle_version_not_set( } releaser.create_resource_version.return_value = { + "id": 1, "version": str(defined_version), - "comment": fake_resource_version["comment"], } resource_sync_manager.is_dirty.return_value = False @@ -344,7 +359,7 @@ def test_handle_version_not_set( ) releaser.release.assert_called_once_with( version=str(defined_version), - comment=fake_resource_version["comment"], + comment="", stage_names=stage, ) diff --git a/sdks/apigw-manager/tests/apigw_manager/apigw/management/commands/test_sync_apigw_stage_mcp_servers.py b/sdks/apigw-manager/tests/apigw_manager/apigw/management/commands/test_sync_apigw_stage_mcp_servers.py new file mode 100644 index 00000000..3535fd4a --- /dev/null +++ b/sdks/apigw-manager/tests/apigw_manager/apigw/management/commands/test_sync_apigw_stage_mcp_servers.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +# TencentBlueKing is pleased to support the open source community by making +# 蓝鲸智云 - PaaS 平台 (BlueKing - PaaS System) available. +# Copyright (C) Tencent. All rights reserved. +# Licensed under the MIT License (the "License"); you may not use this file except +# in compliance with the License. You may obtain a copy of the License at +# +# http://opensource.org/licenses/MIT +# +# Unless required by applicable law or agreed to in writing, software distributed under +# the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, +# either express or implied. See the License for the specific language governing permissions and +# limitations under the License. +# +# We undertake not to change the open source license (MIT license) applicable +# to the current version of the project delivered to anyone in the future. + +import pytest + +from apigw_manager.apigw.management.commands.sync_apigw_stage_mcp_servers import Command + + +@pytest.fixture() +def command(): + return Command() + + +def test_do_uses_v2_result_data_directly(mock_manager, command, capsys): + stage_definition = { + "name": "prod", + "mcp_servers": [ + { + "name": "demo", + "description": "demo", + "resource_names": ["get_user"], + "status": "active", + } + ], + } + mock_manager.sync_stage_mcp_servers.return_value = [ + {"id": 1, "name": "gateway-prod-demo", "action": "create"}, + {"id": 2, "name": "gateway-prod-demo-2", "action": "update"}, + ] + + command.do(mock_manager, [stage_definition]) + + mock_manager.sync_stage_mcp_servers.assert_called_once_with(**stage_definition) + captured = capsys.readouterr() + assert ( + "API gateway stage mcp servers synchronization completed [ id:1,name:gateway-prod-demo,action:create ]" + in captured.out + ) + assert ( + "API gateway stage mcp servers synchronization completed [ id:2,name:gateway-prod-demo-2,action:update ]" + in captured.out + ) + + +def test_do_ignores_empty_v2_result(mock_manager, command, capsys): + stage_definition = { + "name": "prod", + "mcp_servers": [], + } + mock_manager.sync_stage_mcp_servers.return_value = [] + + command.do(mock_manager, [stage_definition]) + + mock_manager.sync_stage_mcp_servers.assert_called_once_with(**stage_definition) + captured = capsys.readouterr() + assert captured.out == "" diff --git a/sdks/apigw-manager/tests/apigw_manager/core/test_hander.py b/sdks/apigw-manager/tests/apigw_manager/core/test_hander.py index a4615091..bee0e7ee 100644 --- a/sdks/apigw-manager/tests/apigw_manager/core/test_hander.py +++ b/sdks/apigw-manager/tests/apigw_manager/core/test_hander.py @@ -18,7 +18,7 @@ import pytest from faker import Faker -from apigw_manager.core.exceptions import ApiResponseError +from apigw_manager.core.exceptions import ApiResponseError, ApiResultError from apigw_manager.core.handler import Handler from bkapi_client_core.exceptions import HTTPResponseError @@ -74,58 +74,76 @@ def test_put_into_cache_has_set(self, handler: Handler, api_cache, operation_id, assert handler._put_into_cache(operation_id, api_data, {}) - def test_call_with_cache(self, handler: Handler, operation, operation_id, faker, mocker): - result = faker.pydict() - operation.return_value = result - mock_get_from_cache = mocker.patch.object(Handler, "_get_from_cache", return_value=(False, None)) - mock_put_into_cache = mocker.patch.object(Handler, "_put_into_cache", return_value=None) - - gateway_name = faker.pystr() - kwargs = { - "gateway_name": gateway_name, - "foo": "bar", - } - - handler._call_with_cache(operation, **kwargs) - - cache_key = { - "gateway_name": gateway_name, - "kwargs": {"gateway_name": gateway_name, "foo": "bar"}, - } - mock_get_from_cache.assert_called_once_with(operation_id, cache_key) - mock_put_into_cache.assert_called_once_with(operation_id, cache_key, result) - - def test_call_connect_error(self, handler: Handler, operation): - operation.side_effect = HTTPResponseError() - with pytest.raises(ApiResponseError): - handler._call(operation) - - def test_call_server_error(self, handler: Handler, operation, mocker): - operation.side_effect = HTTPResponseError(response=mocker.MagicMock(status_code=500)) - with pytest.raises(ApiResponseError): - handler._call(operation) - - def test_call_request_error(self, handler: Handler, operation, mocker): + def test_call_v2_request_error_parse_error_body(self, handler: Handler, operation, mocker): + operation.path = "/api/v2/sync/gateways/{gateway_name}/" operation.side_effect = HTTPResponseError( response=mocker.MagicMock( status_code=400, - json=mocker.MagicMock(return_value={"code": "400", "message": "request error"}), + json=mocker.MagicMock( + return_value={ + "error": { + "code": "AuthFailure", + "message": "The provided credentials could not be validated.", + "system": "bkiam", + "details": [ + { + "code": "SignatureFailure", + "message": "", + "module": "auth", + "links": "", + "doc": "", + } + ], + "data": {}, + } + } + ), ) ) - with pytest.raises(ApiResponseError): - handler._call(operation) + with pytest.raises(ApiResultError) as err: + handler._call_v2(operation) + + assert err.value.code == "AuthFailure" + assert err.value.message == "The provided credentials could not be validated." - def test_call_request_error_with_no_json(self, handler: Handler, operation, mocker): + def test_call_v2_request_error_with_no_json(self, handler: Handler, operation, mocker): + operation.path = "/api/v2/sync/gateways/{gateway_name}/" operation.side_effect = HTTPResponseError( response=mocker.MagicMock( - status_code=400, + status_code=500, + text="server error", json=mocker.MagicMock(side_effect=ValueError()), ) ) - with pytest.raises(ApiResponseError): - handler._call(operation) + with pytest.raises(ApiResultError) as err: + handler._call_v2(operation) + + assert err.value.code == 500 + assert err.value.message == "server error" + + def test_call_v2_connect_error(self, handler: Handler, operation): + operation.path = "/api/v2/sync/gateways/{gateway_name}/" + operation.side_effect = HTTPResponseError() + + with pytest.raises(ApiResponseError) as err: + handler._call_v2(operation) + + assert not isinstance(err.value, ApiResultError) + + @pytest.mark.parametrize( + "data", + [ + {}, + [], + {"id": 1, "name": "gateway"}, + ], + ) + def test_parse_v2_result_returns_data_for_success_response(self, handler: Handler, data): + result = {"data": data} + + assert handler._parse_v2_result(result) == data def test_get_tenant_id_from_config(self, handler: Handler, mocker): handler.config.bk_app_tenant_id = "123" diff --git a/sdks/apigw-manager/tests/apigw_manager/core/test_utils.py b/sdks/apigw-manager/tests/apigw_manager/core/test_utils.py index 350739b2..e45d6011 100644 --- a/sdks/apigw-manager/tests/apigw_manager/core/test_utils.py +++ b/sdks/apigw-manager/tests/apigw_manager/core/test_utils.py @@ -17,7 +17,7 @@ import pytest -from apigw_manager.core.utils import get_item, itemgetter +from apigw_manager.core.utils import get_item @pytest.fixture() @@ -41,9 +41,3 @@ def test_key_not_found(self, test_data): with pytest.raises(KeyError): get_item(test_data, ["a", "nothing"]) - - -class TestItemGetter: - def test_usage(self, test_data): - getter = itemgetter("a", "color") - assert getter(test_data) == test_data["a"]["color"]