diff --git a/CHANGELOG.md b/CHANGELOG.md index bb8a3d4..f543719 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.3.11] - 2026-08-28 + +### Added + +- Add `batchCreateItems` (`POST .../items/batch-create`), `batchUpdateItems` (`PUT .../items/batch-update`), and `batchDeleteItems` (`POST .../items/batch-delete`) APIs for submitting a list of namespace items to create, update, or delete in one call. + ## [0.3.9] - 2026-06-10 ### Added diff --git a/apollo-openapi.yaml b/apollo-openapi.yaml index bed0f6c..45f420b 100644 --- a/apollo-openapi.yaml +++ b/apollo-openapi.yaml @@ -18,7 +18,7 @@ info:
curl -X GET "http://localhost:8070/openapi/v1/apps" \
     -H "Authorization: your_token_here"
- version: 0.3.9 + version: 0.3.11 security: - ApiKeyAuth: [] tags: @@ -945,6 +945,215 @@ paths: schema: $ref: '#/components/schemas/ExceptionResponse' headers: {} + /openapi/v1/envs/{env}/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/items/batch-create: + post: + summary: 批量创建配置项 (new added) + operationId: batchCreateItems + deprecated: false + description: >- + POST + /openapi/v1/envs/{env}/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/items/batch-create + tags: + - Item Management + parameters: + - name: appId + in: path + description: '应用ID' + required: true + schema: + type: string + - name: env + in: path + description: '环境标识' + required: true + schema: + type: string + - name: clusterName + in: path + description: '集群名称' + required: true + schema: + type: string + - name: namespaceName + in: path + description: '命名空间名称' + required: true + schema: + type: string + - name: operator + in: query + description: '操作人用户名' + required: false + schema: + type: string + requestBody: + content: + application/json: + schema: + type: array + minItems: 1 + items: + $ref: '#/components/schemas/OpenItemDTO' + required: true + responses: + '200': + description: '批量创建配置项成功' + '400': + description: '请求参数错误' + content: + application/json: + schema: + $ref: '#/components/schemas/ExceptionResponse' + '403': + description: '权限不足' + content: + application/json: + schema: + $ref: '#/components/schemas/ExceptionResponse' + /openapi/v1/envs/{env}/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/items/batch-update: + put: + summary: 批量修改配置项 (new added) + operationId: batchUpdateItems + deprecated: false + description: >- + PUT + /openapi/v1/envs/{env}/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/items/batch-update + tags: + - Item Management + parameters: + - name: appId + in: path + description: '应用ID' + required: true + schema: + type: string + - name: env + in: path + description: '环境标识' + required: true + schema: + type: string + - name: clusterName + in: path + description: '集群名称' + required: true + schema: + type: string + - name: namespaceName + in: path + description: '命名空间名称' + required: true + schema: + type: string + - name: operator + in: query + description: '操作人用户名' + required: false + schema: + type: string + requestBody: + content: + application/json: + schema: + type: array + minItems: 1 + items: + $ref: '#/components/schemas/OpenItemDTO' + required: true + responses: + '200': + description: '批量修改配置项成功' + '400': + description: '请求参数错误' + content: + application/json: + schema: + $ref: '#/components/schemas/ExceptionResponse' + '403': + description: '权限不足' + content: + application/json: + schema: + $ref: '#/components/schemas/ExceptionResponse' + '404': + description: '配置项不存在' + content: + application/json: + schema: + $ref: '#/components/schemas/ExceptionResponse' + /openapi/v1/envs/{env}/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/items/batch-delete: + post: + summary: 批量删除配置项 (new added) + operationId: batchDeleteItems + deprecated: false + description: >- + POST + /openapi/v1/envs/{env}/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/items/batch-delete + tags: + - Item Management + parameters: + - name: appId + in: path + description: '应用ID' + required: true + schema: + type: string + - name: env + in: path + description: '环境标识' + required: true + schema: + type: string + - name: clusterName + in: path + description: '集群名称' + required: true + schema: + type: string + - name: namespaceName + in: path + description: '命名空间名称' + required: true + schema: + type: string + - name: operator + in: query + description: '操作人用户名' + required: false + schema: + type: string + requestBody: + content: + application/json: + schema: + type: array + minItems: 1 + items: + type: string + minLength: 1 + description: 待删除的配置项 key 列表 + required: true + responses: + '200': + description: '批量删除配置项成功' + '400': + description: '请求参数错误' + content: + application/json: + schema: + $ref: '#/components/schemas/ExceptionResponse' + '403': + description: '权限不足' + content: + application/json: + schema: + $ref: '#/components/schemas/ExceptionResponse' + '404': + description: '配置项不存在' + content: + application/json: + schema: + $ref: '#/components/schemas/ExceptionResponse' /openapi/v1/envs/{env}/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/encodedItems/{key}: get: summary: 通过查询参数获取配置项(支持编码的key) (original openapi) diff --git a/tests/test_item_batch_contract.py b/tests/test_item_batch_contract.py new file mode 100644 index 0000000..7ffa42c --- /dev/null +++ b/tests/test_item_batch_contract.py @@ -0,0 +1,104 @@ +import unittest +from pathlib import Path + +import yaml + + +SPEC_FILES = ( + "apollo-openapi.yaml", +) + +BASE_PATH = ( + "/openapi/v1/envs/{env}/apps/{appId}/clusters/{clusterName}" + "/namespaces/{namespaceName}/items" +) + + +class ItemBatchContractTest(unittest.TestCase): + + def setUp(self): + self.repo_root = Path(__file__).resolve().parents[1] + + def _load_spec(self, spec_file): + return yaml.safe_load((self.repo_root / spec_file).read_text(encoding="utf-8")) + + def test_item_batch_paths_use_generated_contract(self): + for spec_file in SPEC_FILES: + spec = self._load_spec(spec_file) + + with self.subTest(spec=spec_file): + tag_names = {tag["name"] for tag in spec["tags"]} + self.assertIn("Item Management", tag_names) + + expected_operations = { + f"{BASE_PATH}/batch-create": ("post", "batchCreateItems"), + f"{BASE_PATH}/batch-update": ("put", "batchUpdateItems"), + f"{BASE_PATH}/batch-delete": ("post", "batchDeleteItems"), + } + for path, (method, operation_id) in expected_operations.items(): + operation = spec["paths"][path][method] + self.assertEqual(operation_id, operation["operationId"]) + self.assertEqual(["Item Management"], operation["tags"]) + self.assertEqual( + "#/components/schemas/ExceptionResponse", + operation["responses"]["403"]["content"]["application/json"]["schema"]["$ref"], + ) + + parameter_names = {parameter["name"] for parameter in operation["parameters"]} + self.assertEqual( + {"appId", "env", "clusterName", "namespaceName", "operator"}, + parameter_names, + ) + operator_parameter = next( + parameter for parameter in operation["parameters"] + if parameter["name"] == "operator") + self.assertFalse(operator_parameter["required"]) + + def test_batch_create_and_update_accept_item_list(self): + for spec_file in SPEC_FILES: + spec = self._load_spec(spec_file) + + with self.subTest(spec=spec_file): + for path, method in ( + (f"{BASE_PATH}/batch-create", "post"), + (f"{BASE_PATH}/batch-update", "put"), + ): + operation = spec["paths"][path][method] + request_schema = operation["requestBody"]["content"]["application/json"]["schema"] + self.assertEqual("array", request_schema["type"]) + self.assertEqual(1, request_schema["minItems"]) + self.assertEqual( + "#/components/schemas/OpenItemDTO", + request_schema["items"]["$ref"], + ) + + update_operation = spec["paths"][f"{BASE_PATH}/batch-update"]["put"] + self.assertEqual( + "#/components/schemas/ExceptionResponse", + update_operation["responses"]["404"]["content"]["application/json"]["schema"][ + "$ref"], + ) + + def test_batch_delete_accepts_key_list(self): + for spec_file in SPEC_FILES: + spec = self._load_spec(spec_file) + + with self.subTest(spec=spec_file): + operation = spec["paths"][f"{BASE_PATH}/batch-delete"]["post"] + request_schema = operation["requestBody"]["content"]["application/json"]["schema"] + self.assertEqual("array", request_schema["type"]) + self.assertEqual(1, request_schema["minItems"]) + self.assertEqual("string", request_schema["items"]["type"]) + self.assertEqual(1, request_schema["items"]["minLength"]) + self.assertEqual( + "#/components/schemas/ExceptionResponse", + operation["responses"]["400"]["content"]["application/json"]["schema"]["$ref"], + ) + self.assertEqual( + "#/components/schemas/ExceptionResponse", + operation["responses"]["404"]["content"]["application/json"]["schema"]["$ref"], + ) + + +if __name__ == "__main__": + unittest.main()