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()