This commit is contained in:
2026-08-25 12:27:47 +08:00
parent 0e0b6f8264
commit 4b5e5a1e4e
13 changed files with 183 additions and 33 deletions
+1 -1
View File
@@ -886,7 +886,7 @@ components:
TenantCreationSuccess: {description: Enterprise tenant and one-time owner credentials returned / 返回企业客户及一次性管理员凭据, content: {application/json: {schema: {$ref: '#/components/schemas/TenantCreationApiResponse'}}}}
BadRequest: {description: Invalid request / 请求参数错误, content: {application/json: {schema: {$ref: '#/components/schemas/ApiResponse'}}}}
Unauthorized: {description: Authentication required / 需要登录, content: {application/json: {schema: {$ref: '#/components/schemas/ApiResponse'}}}}
Forbidden: {description: Permission, customer scope or platform technical scope rejected / 权限、客户范围或平台技术作用域不匹配, content: {application/json: {schema: {$ref: '#/components/schemas/ApiResponse'}}}}
Forbidden: {description: 'Permission, customer scope or platform technical scope rejected / 权限、客户范围或平台技术作用域不匹配', content: {application/json: {schema: {$ref: '#/components/schemas/ApiResponse'}}}}
NotFound: {description: Resource not found / 资源不存在, content: {application/json: {schema: {$ref: '#/components/schemas/ApiResponse'}}}}
Conflict: {description: Resource conflict / 资源冲突, content: {application/json: {schema: {$ref: '#/components/schemas/ApiResponse'}}}}
ServiceUnavailable: {description: Authentication service unavailable / 认证服务不可用, content: {application/json: {schema: {$ref: '#/components/schemas/ApiResponse'}}}}
@@ -60,7 +60,7 @@ paths:
- {$ref: '#/components/parameters/Size'}
- {name: templateCode, in: query, schema: {type: string, maxLength: 64}}
- {name: category, in: query, schema: {type: string, maxLength: 64}}
- {name: title, in: query, description: Fuzzy match template name or title template, schema: {type: string, maxLength: 128}}
- {name: title, in: query, description: 'Fuzzy match template name or title template', schema: {type: string, maxLength: 128}}
- {name: status, in: query, schema: {type: string, enum: [DRAFT, ACTIVE, DISABLED]}}
responses: {'200': {$ref: '#/components/responses/TemplatePage'}}
post:
@@ -92,7 +92,7 @@ paths:
- {$ref: '#/components/parameters/Size'}
- {name: taskNo, in: query, schema: {type: string, maxLength: 64}}
- {name: category, in: query, schema: {type: string, maxLength: 64}}
- {name: title, in: query, description: Fuzzy match rendered title, schema: {type: string, maxLength: 128}}
- {name: title, in: query, description: 'Fuzzy match rendered title', schema: {type: string, maxLength: 128}}
- {name: status, in: query, schema: {type: string, enum: [CREATED, SCHEDULED, DISPATCHING, RETRYING, SENT, DEAD, CANCELLED]}}
responses: {'200': {$ref: '#/components/responses/TaskPage'}}
/platform/v1/notification-tasks/{id}:
@@ -145,17 +145,27 @@ components:
tenantId: {type: integer, format: int64, minimum: 0}
templateCode: {type: string, maxLength: 64}
recipientType: {type: string, enum: [USER, MEMBERSHIP, TENANT_ROLE, TENANT_ALL, PLATFORM_ROLE]}
recipientIds: {type: array, maxItems: 1000, items: {type: integer, format: int64}, description: Required for USER or MEMBERSHIP; forbidden for role/all recipient types.}
roleCode: {type: string, maxLength: 64, description: Required for TENANT_ROLE or PLATFORM_ROLE; forbidden otherwise.}
recipientIds: {type: array, minItems: 1, maxItems: 1000, items: {type: integer, format: int64}, description: Required for USER or MEMBERSHIP; forbidden for role/all recipient types.}
roleCode: {type: string, minLength: 1, maxLength: 64, description: Required for TENANT_ROLE or PLATFORM_ROLE; forbidden otherwise.}
variables: {type: object, maxProperties: 32, additionalProperties: {type: string, maxLength: 2000}}
scheduledAt: {type: string, format: date-time, nullable: true}
idempotencyKey: {type: string, minLength: 8, maxLength: 128}
sourceEventId: {type: string, maxLength: 128, nullable: true}
allOf:
- if: {properties: {recipientType: {enum: [USER, MEMBERSHIP]}}}
then: {required: [recipientIds]}
- if: {properties: {recipientType: {enum: [TENANT_ROLE, PLATFORM_ROLE]}}}
then: {required: [roleCode]}
oneOf:
- required: [recipientIds]
properties:
recipientType: {type: string, enum: [USER, MEMBERSHIP]}
not: {required: [roleCode]}
- required: [roleCode]
properties:
recipientType: {type: string, enum: [TENANT_ROLE, PLATFORM_ROLE]}
not: {required: [recipientIds]}
- properties:
recipientType: {type: string, enum: [TENANT_ALL]}
not:
anyOf:
- required: [recipientIds]
- required: [roleCode]
CreateTemplateRequest:
type: object
required: [templateCode, templateName, category, titleTemplate, bodyTemplate, variableWhitelist]
@@ -69,6 +69,33 @@ class NotificationOpenApiContractTest {
assertThat(map(map(taskDetail.get("properties")).get("attemptCount")))
.containsEntry("minimum", 0).containsEntry("maximum", 3);
assertThat(document).contains("scheduler delivery starts after the retry delay");
Map<String, Object> createNotification = map(schemas.get("CreateNotificationRequest"));
Map<String, Object> selectorProperties = map(createNotification.get("properties"));
assertThat(map(selectorProperties.get("recipientIds"))).containsEntry("minItems", 1);
assertThat(map(selectorProperties.get("roleCode"))).containsEntry("minLength", 1);
List<Object> selectorBranches = list(createNotification.get("oneOf"));
assertThat(selectorBranches).hasSize(3);
Map<String, Object> directRecipients = map(selectorBranches.get(0));
assertThat(list(directRecipients.get("required"))).containsExactly("recipientIds");
assertThat(list(map(directRecipients.get("not")).get("required"))).containsExactly("roleCode");
assertThat(list(map(map(directRecipients.get("properties")).get("recipientType")).get("enum")))
.containsExactly("USER", "MEMBERSHIP");
Map<String, Object> roleRecipients = map(selectorBranches.get(1));
assertThat(list(roleRecipients.get("required"))).containsExactly("roleCode");
assertThat(list(map(roleRecipients.get("not")).get("required"))).containsExactly("recipientIds");
assertThat(list(map(map(roleRecipients.get("properties")).get("recipientType")).get("enum")))
.containsExactly("TENANT_ROLE", "PLATFORM_ROLE");
Map<String, Object> allRecipients = map(selectorBranches.get(2));
assertThat(list(map(map(allRecipients.get("properties")).get("recipientType")).get("enum")))
.containsExactly("TENANT_ALL");
assertThat(list(map(allRecipients.get("not")).get("anyOf")))
.extracting(value -> list(map(value).get("required")))
.containsExactly(List.of("recipientIds"), List.of("roleCode"));
assertThat(document).doesNotContain("if:").doesNotContain("then:");
}
private void assertRequestBody(Map<String, Object> paths, String path, String method) {