diff --git a/src/dashboard/apigateway/apigateway/apis/open/gateway/serializers.py b/src/dashboard/apigateway/apigateway/apis/open/gateway/serializers.py index f5739a04b..10b6853e5 100644 --- a/src/dashboard/apigateway/apigateway/apis/open/gateway/serializers.py +++ b/src/dashboard/apigateway/apigateway/apis/open/gateway/serializers.py @@ -55,9 +55,8 @@ def get_user_auth_type(self, obj): return self.context["gateway_auth_configs"][obj.id].user_auth_type def get_maintainers(self, obj): - # 网关对外的维护者,便于用户咨询网关问题,默认使用开发者;maintainers 为有权限管理网关的管理者 - # TODO: 是否应该使用开发者,如何让网关管理员知道开发者用于 API 文档中展示为网关的维护者 - return obj.developers or obj.maintainers + # TODO: 网关对外的维护者(助手号),便于用户咨询网关问题,需要单独使用一个新字段去维护? + return obj.maintainers class GatewayRetrieveV1OutputSLZ(GatewayListV1OutputSLZ): diff --git a/src/dashboard/apigateway/apigateway/apis/web/gateway/serializers.py b/src/dashboard/apigateway/apigateway/apis/web/gateway/serializers.py index 9ee467173..7f7c235ef 100644 --- a/src/dashboard/apigateway/apigateway/apis/web/gateway/serializers.py +++ b/src/dashboard/apigateway/apigateway/apis/web/gateway/serializers.py @@ -20,6 +20,7 @@ from rest_framework.validators import UniqueTogetherValidator from tencent_apigateway_common.i18n.field import SerializerTranslatedField +from apigateway.biz.constants import APP_CODE_PATTERN from apigateway.biz.gateway import GatewayHandler from apigateway.biz.gateway_type import GatewayTypeHandler from apigateway.core.constants import ( @@ -83,6 +84,9 @@ class GatewayCreateInputSLZ(serializers.ModelSerializer): developers = serializers.ListField( child=serializers.CharField(), allow_empty=True, default=list, help_text="网关开发者" ) + bk_app_codes = serializers.ListField( + child=serializers.RegexField(APP_CODE_PATTERN), allow_empty=True, required=False, help_text="网关关联的应用" + ) class Meta: model = Gateway @@ -92,6 +96,7 @@ class Meta: "maintainers", "developers", "is_public", + "bk_app_codes", ) lookup_field = "id" @@ -140,6 +145,7 @@ class GatewayRetrieveOutputSLZ(serializers.ModelSerializer): docs_url = serializers.SerializerMethodField(help_text="文档地址") public_key_fingerprint = serializers.SerializerMethodField(help_text="公钥(指纹)") allow_update_gateway_auth = serializers.SerializerMethodField(help_text="是否允许更新网关认证配置") + bk_app_codes = serializers.SerializerMethodField(help_text="网关关联的应用") class Meta: model = Gateway @@ -159,6 +165,7 @@ class Meta: "api_domain", "docs_url", "public_key_fingerprint", + "bk_app_codes", ) read_only_fields = fields lookup_field = "id" @@ -199,12 +206,18 @@ def get_allow_update_gateway_auth(self, obj): def get_is_official(self, obj): return GatewayTypeHandler.is_official(self.context["auth_config"].gateway_type) + def get_bk_app_codes(self, obj): + return self.context["bk_app_codes"] + class GatewayUpdateInputSLZ(serializers.ModelSerializer): maintainers = serializers.ListField(child=serializers.CharField(), allow_empty=True, help_text="网关维护人员") developers = serializers.ListField( child=serializers.CharField(), allow_empty=True, default=list, help_text="网关开发者" ) + bk_app_codes = serializers.ListField( + child=serializers.RegexField(APP_CODE_PATTERN), allow_empty=True, required=False, help_text="网关关联的应用" + ) class Meta: model = Gateway @@ -213,6 +226,7 @@ class Meta: "maintainers", "developers", "is_public", + "bk_app_codes", ) lookup_field = "id" diff --git a/src/dashboard/apigateway/apigateway/apis/web/gateway/views.py b/src/dashboard/apigateway/apigateway/apis/web/gateway/views.py index 6431bc6d3..66d526add 100644 --- a/src/dashboard/apigateway/apigateway/apis/web/gateway/views.py +++ b/src/dashboard/apigateway/apigateway/apis/web/gateway/views.py @@ -27,6 +27,7 @@ from apigateway.apis.web.constants import UserAuthTypeEnum from apigateway.apps.audit.constants import OpTypeEnum from apigateway.biz.gateway import GatewayHandler +from apigateway.biz.gateway_app_binding import GatewayAppBindingHandler from apigateway.common.contexts import GatewayAuthContext from apigateway.common.error_codes import error_codes from apigateway.common.release.publish import trigger_gateway_publish @@ -104,6 +105,8 @@ def create(self, request, *args, **kwargs): slz = GatewayCreateInputSLZ(data=request.data, context={"created_by": request.user.username}) slz.is_valid(raise_exception=True) + bk_app_codes = slz.validated_data.pop("bk_app_codes", None) + # 1. save gateway slz.save( status=GatewayStatusEnum.ACTIVE.value, @@ -116,6 +119,7 @@ def create(self, request, *args, **kwargs): gateway=slz.instance, user_auth_type=UserAuthTypeEnum(settings.DEFAULT_USER_AUTH_TYPE).value, username=request.user.username, + app_codes_to_binding=bk_app_codes, ) # 3. record audit log @@ -175,6 +179,7 @@ def retrieve(self, request, *args, **kwargs): instance, context={ "auth_config": GatewayAuthContext().get_auth_config(instance.pk), + "bk_app_codes": GatewayAppBindingHandler.get_bound_app_codes(instance), }, ) return OKJsonResponse(data=slz.data) @@ -186,8 +191,13 @@ def update(self, request, *args, **kwargs): slz = GatewayUpdateInputSLZ(instance=instance, data=request.data, partial=partial) slz.is_valid(raise_exception=True) + bk_app_codes = slz.validated_data.pop("bk_app_codes", None) + slz.save(updated_by=request.user.username) + if bk_app_codes is not None: + GatewayAppBindingHandler.update_gateway_app_bindings(instance, bk_app_codes) + GatewayHandler.record_audit_log_success( username=request.user.username, gateway_id=instance.id, diff --git a/src/dashboard/apigateway/apigateway/apps/gateway/__init__.py b/src/dashboard/apigateway/apigateway/apps/gateway/__init__.py new file mode 100644 index 000000000..2941673fe --- /dev/null +++ b/src/dashboard/apigateway/apigateway/apps/gateway/__init__.py @@ -0,0 +1,17 @@ +# +# TencentBlueKing is pleased to support the open source community by making +# 蓝鲸智云 - API 网关(BlueKing - APIGateway) available. +# Copyright (C) 2017 THL A29 Limited, a Tencent company. 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. +# diff --git a/src/dashboard/apigateway/apigateway/apps/gateway/admin.py b/src/dashboard/apigateway/apigateway/apps/gateway/admin.py new file mode 100644 index 000000000..6ea25544f --- /dev/null +++ b/src/dashboard/apigateway/apigateway/apps/gateway/admin.py @@ -0,0 +1,29 @@ +# +# TencentBlueKing is pleased to support the open source community by making +# 蓝鲸智云 - API 网关(BlueKing - APIGateway) available. +# Copyright (C) 2017 THL A29 Limited, a Tencent company. 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. +# +from django.contrib import admin + +from .models import GatewayAppBinding + + +class GatewayAppBindingAdmin(admin.ModelAdmin): + list_display = ["id", "gateway", "bk_app_code", "updated_time"] + search_fields = ["gateway__id", "bk_app_code"] + list_filter = ["gateway"] + + +admin.site.register(GatewayAppBinding, GatewayAppBindingAdmin) diff --git a/src/dashboard/apigateway/apigateway/apps/gateway/apps.py b/src/dashboard/apigateway/apigateway/apps/gateway/apps.py new file mode 100644 index 000000000..d8a55cefc --- /dev/null +++ b/src/dashboard/apigateway/apigateway/apps/gateway/apps.py @@ -0,0 +1,22 @@ +# +# TencentBlueKing is pleased to support the open source community by making +# 蓝鲸智云 - API 网关(BlueKing - APIGateway) available. +# Copyright (C) 2017 THL A29 Limited, a Tencent company. 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. +# +from django.apps import AppConfig + + +class GatewayConfig(AppConfig): + name = "apigateway.apps.gateway" diff --git a/src/dashboard/apigateway/apigateway/apps/gateway/migrations/0001_initial.py b/src/dashboard/apigateway/apigateway/apps/gateway/migrations/0001_initial.py new file mode 100644 index 000000000..2f7434290 --- /dev/null +++ b/src/dashboard/apigateway/apigateway/apps/gateway/migrations/0001_initial.py @@ -0,0 +1,49 @@ +# +# TencentBlueKing is pleased to support the open source community by making +# 蓝鲸智云 - API 网关(BlueKing - APIGateway) available. +# Copyright (C) 2017 THL A29 Limited, a Tencent company. 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. +# +# Generated by Django 3.2.18 on 2023-11-08 04:35 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('core', '0032_gateway__developers'), + ] + + operations = [ + migrations.CreateModel( + name='GatewayAppBinding', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_time', models.DateTimeField(auto_now_add=True, null=True)), + ('updated_time', models.DateTimeField(auto_now=True, null=True)), + ('created_by', models.CharField(blank=True, max_length=32, null=True)), + ('updated_by', models.CharField(blank=True, max_length=32, null=True)), + ('bk_app_code', models.CharField(db_index=True, max_length=32)), + ('gateway', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.gateway')), + ], + options={ + 'db_table': 'gateway_app_binding', + 'unique_together': {('gateway', 'bk_app_code')}, + }, + ), + ] diff --git a/src/dashboard/apigateway/apigateway/apps/gateway/migrations/__init__.py b/src/dashboard/apigateway/apigateway/apps/gateway/migrations/__init__.py new file mode 100644 index 000000000..2941673fe --- /dev/null +++ b/src/dashboard/apigateway/apigateway/apps/gateway/migrations/__init__.py @@ -0,0 +1,17 @@ +# +# TencentBlueKing is pleased to support the open source community by making +# 蓝鲸智云 - API 网关(BlueKing - APIGateway) available. +# Copyright (C) 2017 THL A29 Limited, a Tencent company. 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. +# diff --git a/src/dashboard/apigateway/apigateway/apps/gateway/models.py b/src/dashboard/apigateway/apigateway/apps/gateway/models.py new file mode 100644 index 000000000..20ddee7eb --- /dev/null +++ b/src/dashboard/apigateway/apigateway/apps/gateway/models.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# +# TencentBlueKing is pleased to support the open source community by making +# 蓝鲸智云 - API 网关(BlueKing - APIGateway) available. +# Copyright (C) 2017 THL A29 Limited, a Tencent company. 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. +# +from django.db import models + +from apigateway.common.mixins.models import OperatorModelMixin, TimestampedModelMixin +from apigateway.core.models import Gateway + + +class GatewayAppBinding(TimestampedModelMixin, OperatorModelMixin): + """ + 网关绑定的蓝鲸应用 + - 仅影响 HomePage 中运维开发分数的计算 + """ + + gateway = models.ForeignKey(Gateway, on_delete=models.CASCADE) + bk_app_code = models.CharField(max_length=32, db_index=True) + + def __str__(self): + return f"" + + class Meta: + db_table = "gateway_app_binding" + unique_together = ("gateway", "bk_app_code") diff --git a/src/dashboard/apigateway/apigateway/biz/gateway/gateway.py b/src/dashboard/apigateway/apigateway/biz/gateway/gateway.py index f35d7cae1..9fe47421b 100644 --- a/src/dashboard/apigateway/apigateway/biz/gateway/gateway.py +++ b/src/dashboard/apigateway/apigateway/biz/gateway/gateway.py @@ -28,6 +28,7 @@ from apigateway.apps.monitor.models import AlarmStrategy from apigateway.apps.plugin.models import PluginBinding from apigateway.apps.support.models import ReleasedResourceDoc +from apigateway.biz.gateway_app_binding import GatewayAppBindingHandler from apigateway.biz.gateway_jwt import GatewayJWTHandler from apigateway.biz.gateway_related_app import GatewayRelatedAppHandler from apigateway.biz.iam import IAMHandler @@ -149,6 +150,7 @@ def save_related_data( user_config: Optional[dict] = None, unfiltered_sensitive_keys: Optional[List[str]] = None, api_type: Optional[GatewayTypeEnum] = None, + app_codes_to_binding: Optional[List[str]] = None, ): # 1. save gateway auth_config GatewayHandler.save_auth_config( @@ -175,7 +177,11 @@ def save_related_data( if related_app_code: GatewayRelatedAppHandler.add_related_app(gateway.id, related_app_code) - # 6. 在权限中心注册分级管理员,创建用户组 + # 6. update gateway app binding + if app_codes_to_binding is not None: + GatewayAppBindingHandler.update_gateway_app_bindings(gateway, app_codes_to_binding) + + # 7. 在权限中心注册分级管理员,创建用户组 if settings.USE_BK_IAM_PERMISSION: IAMHandler.register_grade_manager_and_builtin_user_groups(gateway) diff --git a/src/dashboard/apigateway/apigateway/biz/gateway_app_binding.py b/src/dashboard/apigateway/apigateway/biz/gateway_app_binding.py new file mode 100644 index 000000000..e6bde578b --- /dev/null +++ b/src/dashboard/apigateway/apigateway/biz/gateway_app_binding.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +# +# TencentBlueKing is pleased to support the open source community by making +# 蓝鲸智云 - API 网关(BlueKing - APIGateway) available. +# Copyright (C) 2017 THL A29 Limited, a Tencent company. 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. +# +from typing import List + +from apigateway.apps.gateway.models import GatewayAppBinding +from apigateway.core.models import Gateway + + +class GatewayAppBindingHandler: + @classmethod + def update_gateway_app_bindings(cls, gateway: Gateway, bk_app_codes: List[str]): + """ + 更新网关应用的绑定 + - 1. 如果 bk_app_codes 中应用未绑定,则新增绑定 + - 2. 如果已绑定的应用未在 bk_app_codes 中,则删除 + """ + bound_app_codes = cls.get_bound_app_codes(gateway) + app_codes_to_add = set(bk_app_codes) - set(bound_app_codes) + app_codes_to_delete = set(bound_app_codes) - set(bk_app_codes) + + if app_codes_to_add: + GatewayAppBinding.objects.bulk_create( + [GatewayAppBinding(gateway=gateway, bk_app_code=code) for code in app_codes_to_add] + ) + + if app_codes_to_delete: + GatewayAppBinding.objects.filter(gateway=gateway, bk_app_code__in=app_codes_to_delete).delete() + + @staticmethod + def get_bound_app_codes(gateway: Gateway) -> List[str]: + """获取已绑定的应用""" + return list(GatewayAppBinding.objects.filter(gateway=gateway).values_list("bk_app_code", flat=True)) diff --git a/src/dashboard/apigateway/apigateway/conf/default.py b/src/dashboard/apigateway/apigateway/conf/default.py index 5f719fd11..2f79bd706 100644 --- a/src/dashboard/apigateway/apigateway/conf/default.py +++ b/src/dashboard/apigateway/apigateway/conf/default.py @@ -88,6 +88,7 @@ "apigateway.apps.monitor", "apigateway.schema", "apigateway.core", + "apigateway.apps.gateway", "apigateway.apps.access_strategy", "apigateway.apps.plugin", "apigateway.apps.label", @@ -790,6 +791,7 @@ "MENU_ITEM_ESB_API_DOC": env.bool("FEATURE_FLAG_MENU_ITEM_ESB_API_DOC", True), "SYNC_ESB_TO_APIGW_ENABLED": env.bool("FEATURE_FLAG_SYNC_ESB_TO_APIGW_ENABLED", True), "GATEWAY_DEVELOPERS_ENABLED": env.bool("FEATURE_FLAG_GATEWAY_DEVELOPERS_ENABLED", False), + "GATEWAY_APP_BINDING_ENABLED": env.bool("FEATURE_FLAG_GATEWAY_APP_BINDING_ENABLED", False), # api-support "ENABLE_SDK": env.bool("FEATURE_FLAG_ENABLE_SDK", False), "ALLOW_CREATE_APPCHAT": env.bool("FEATURE_FLAG_ALLOW_CREATE_APPCHAT", False), diff --git a/src/dashboard/apigateway/apigateway/core/migrations/0038_auto_20231023_1526.py b/src/dashboard/apigateway/apigateway/core/migrations/0038_auto_20231023_1526.py index 71e4d9649..a12ac6606 100644 --- a/src/dashboard/apigateway/apigateway/core/migrations/0038_auto_20231023_1526.py +++ b/src/dashboard/apigateway/apigateway/core/migrations/0038_auto_20231023_1526.py @@ -1,3 +1,20 @@ +# +# TencentBlueKing is pleased to support the open source community by making +# 蓝鲸智云 - API 网关(BlueKing - APIGateway) available. +# Copyright (C) 2017 THL A29 Limited, a Tencent company. 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. +# # Generated by Django 3.2.18 on 2023-10-23 07:26 from django.db import migrations, models diff --git a/src/dashboard/apigateway/apigateway/core/models.py b/src/dashboard/apigateway/apigateway/core/models.py index b6cb467c5..3493d3ba6 100644 --- a/src/dashboard/apigateway/apigateway/core/models.py +++ b/src/dashboard/apigateway/apigateway/core/models.py @@ -709,7 +709,10 @@ class Meta: class GatewayRelatedApp(TimestampedModelMixin): - """网关关联的蓝鲸应用""" + """ + 网关关联的蓝鲸应用 + - 应用可以通过 openapi 操作网关数据 + """ gateway = models.ForeignKey(Gateway, db_column="api_id", on_delete=models.CASCADE) bk_app_code = models.CharField(max_length=32, db_index=True) diff --git a/src/dashboard/apigateway/apigateway/tests/apis/web/gateway/test_serializers.py b/src/dashboard/apigateway/apigateway/tests/apis/web/gateway/test_serializers.py index 8212e62eb..b43cc57ad 100644 --- a/src/dashboard/apigateway/apigateway/tests/apis/web/gateway/test_serializers.py +++ b/src/dashboard/apigateway/apigateway/tests/apis/web/gateway/test_serializers.py @@ -114,6 +114,7 @@ class TestGatewayCreateInputSLZ: "maintainers": ["guest"], "developers": ["t1", "t2"], "is_public": True, + "bk_app_codes": ["app1", "app2"], }, { "name": "test", @@ -121,6 +122,24 @@ class TestGatewayCreateInputSLZ: "maintainers": ["guest", "admin"], "developers": ["t1", "t2"], "is_public": True, + "bk_app_codes": ["app1", "app2"], + }, + ), + # ok, default value + ( + False, + { + "name": "test", + "description": "test", + "maintainers": ["guest"], + "is_public": True, + }, + { + "name": "test", + "description": "test", + "maintainers": ["guest", "admin"], + "developers": [], + "is_public": True, }, ), # name length < 3 @@ -212,6 +231,7 @@ def test_to_representation(self, fake_gateway, mocker): gateway_type=GatewayTypeEnum.CLOUDS_API.value, allow_update_gateway_auth=True, ), + "bk_app_codes": [], }, ) jwt = GatewayJWTHandler.create_jwt(fake_gateway) @@ -232,6 +252,7 @@ def test_to_representation(self, fake_gateway, mocker): "docs_url": "http://apigw.demo.com/docs/", "public_key_fingerprint": calculate_fingerprint(jwt.public_key), "is_official": False, + "bk_app_codes": [], } assert slz.data == expected @@ -247,12 +268,14 @@ class TestGatewayUpdateInputSLZ: "developers": ["foo"], "description": "test", "is_public": True, + "bk_app_codes": ["app1", "app2"], }, { "maintainers": ["admin"], "developers": ["foo"], "description": "test", "is_public": True, + "bk_app_codes": ["app1", "app2"], }, ), # input include status diff --git a/src/dashboard/apigateway/apigateway/tests/apis/web/gateway/test_views.py b/src/dashboard/apigateway/apigateway/tests/apis/web/gateway/test_views.py index a36cfacea..a79004180 100644 --- a/src/dashboard/apigateway/apigateway/tests/apis/web/gateway/test_views.py +++ b/src/dashboard/apigateway/apigateway/tests/apis/web/gateway/test_views.py @@ -15,6 +15,7 @@ # 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. # +from apigateway.apps.gateway.models import GatewayAppBinding from apigateway.biz.gateway import GatewayHandler from apigateway.biz.gateway_jwt import GatewayJWTHandler from apigateway.core.constants import GatewayStatusEnum @@ -38,6 +39,7 @@ def test_create(self, request_view, faker, unique_gateway_name): "description": faker.pystr(), "maintainers": ["admin"], "is_public": False, + "bk_app_codes": ["app1"], } resp = request_view( @@ -53,6 +55,7 @@ def test_create(self, request_view, faker, unique_gateway_name): assert result["data"]["id"] == gateway.id assert Stage.objects.filter(gateway=gateway).exists() assert JWT.objects.filter(gateway=gateway).count() == 1 + assert GatewayAppBinding.objects.filter(gateway=gateway).count() == 1 class TestGatewayRetrieveUpdateDestroyApi: @@ -75,6 +78,7 @@ def test_update(self, request_view, faker, fake_gateway): "description": faker.pystr(), "maintainers": ["admin"], "is_public": faker.random_element([True, False]), + "bk_app_codes": ["app1"], } resp = request_view( method="PUT", @@ -87,6 +91,7 @@ def test_update(self, request_view, faker, fake_gateway): assert resp.status_code == 204 assert gateway.description == data["description"] assert gateway.is_public is data["is_public"] + assert GatewayAppBinding.objects.filter(gateway=gateway).count() == 1 def test_destroy(self, request_view, fake_gateway): fake_gateway.status = GatewayStatusEnum.INACTIVE.value diff --git a/src/dashboard/apigateway/apigateway/tests/biz/gateway/test_gateway.py b/src/dashboard/apigateway/apigateway/tests/biz/gateway/test_gateway.py index 8fe5bf5a8..97a6fb4a8 100644 --- a/src/dashboard/apigateway/apigateway/tests/biz/gateway/test_gateway.py +++ b/src/dashboard/apigateway/apigateway/tests/biz/gateway/test_gateway.py @@ -19,6 +19,7 @@ from django.core.exceptions import ObjectDoesNotExist from django_dynamic_fixture import G +from apigateway.apps.gateway.models import GatewayAppBinding from apigateway.apps.monitor.models import AlarmStrategy from apigateway.apps.support.models import ReleasedResourceDoc from apigateway.biz.gateway import GatewayHandler @@ -30,7 +31,15 @@ GatewayTypeEnum, StageStatusEnum, ) -from apigateway.core.models import JWT, Context, Gateway, GatewayRelatedApp, Release, Resource, Stage +from apigateway.core.models import ( + JWT, + Context, + Gateway, + GatewayRelatedApp, + Release, + Resource, + Stage, +) class TestGatewayHandler: @@ -223,7 +232,7 @@ def test_save_related_data(self, mocker, fake_gateway): } ), ) - GatewayHandler.save_related_data(fake_gateway, "default", "admin", "test") + GatewayHandler.save_related_data(fake_gateway, "default", "admin", "test", app_codes_to_binding=["app1"]) assert Context.objects.filter( scope_type=ContextScopeTypeEnum.GATEWAY.value, @@ -235,6 +244,7 @@ def test_save_related_data(self, mocker, fake_gateway): assert Stage.objects.filter(gateway=fake_gateway).exists() assert AlarmStrategy.objects.filter(gateway=fake_gateway).exists() assert GatewayRelatedApp.objects.filter(gateway=fake_gateway, bk_app_code="test").exists() + assert GatewayAppBinding.objects.filter(gateway=fake_gateway).exists() def test_delete_gateway( self, diff --git a/src/dashboard/apigateway/apigateway/tests/biz/test_gateway_app_binding.py b/src/dashboard/apigateway/apigateway/tests/biz/test_gateway_app_binding.py new file mode 100644 index 000000000..60b132e91 --- /dev/null +++ b/src/dashboard/apigateway/apigateway/tests/biz/test_gateway_app_binding.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# +# TencentBlueKing is pleased to support the open source community by making +# 蓝鲸智云 - API 网关(BlueKing - APIGateway) available. +# Copyright (C) 2017 THL A29 Limited, a Tencent company. 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. +# +from apigateway.apps.gateway.models import GatewayAppBinding +from apigateway.biz.gateway_app_binding import GatewayAppBindingHandler + + +class TestGatewayAppBindingHandler: + def test_update_gateway_app_bindings(self, fake_gateway): + GatewayAppBindingHandler.update_gateway_app_bindings(fake_gateway, ["app1", "app2"]) + assert GatewayAppBinding.objects.filter(gateway=fake_gateway).count() == 2 + + GatewayAppBindingHandler.update_gateway_app_bindings(fake_gateway, ["app3", "app2"]) + assert GatewayAppBinding.objects.filter(gateway=fake_gateway).count() == 2 + + GatewayAppBindingHandler.update_gateway_app_bindings(fake_gateway, ["app1"]) + assert GatewayAppBinding.objects.filter(gateway=fake_gateway).count() == 1 + + GatewayAppBindingHandler.update_gateway_app_bindings(fake_gateway, []) + assert GatewayAppBinding.objects.filter(gateway=fake_gateway).count() == 0 + + def test_get_bound_app_codes(self, fake_gateway): + GatewayAppBindingHandler.update_gateway_app_bindings(fake_gateway, ["app1", "app2"]) + result = GatewayAppBindingHandler.get_bound_app_codes(gateway=fake_gateway) + assert set(result) == {"app1", "app2"}