@steedos-labs/content-compliance 0.8.36 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/dist/index.js +1 -1
  2. package/lib/actions/contentHubMaterialGroups.d.ts +19 -0
  3. package/lib/actions/contentHubMaterialGroups.js +70 -0
  4. package/lib/actions/contentHubMaterialGroups.js.map +1 -0
  5. package/lib/actions/contentHubSyncPreview.d.ts +21 -0
  6. package/lib/actions/contentHubSyncPreview.js +51 -0
  7. package/lib/actions/contentHubSyncPreview.js.map +1 -0
  8. package/lib/actions/contentHubTagValues.d.ts +12 -0
  9. package/lib/actions/contentHubTagValues.js +70 -0
  10. package/lib/actions/contentHubTagValues.js.map +1 -0
  11. package/lib/actions/index.d.ts +3 -0
  12. package/lib/actions/index.js +3 -0
  13. package/lib/actions/index.js.map +1 -1
  14. package/lib/actions/sendToContentHub.js +1 -1
  15. package/lib/actions/sendToContentHub.js.map +1 -1
  16. package/lib/actions/spotCheckMaterial.js +2 -1
  17. package/lib/actions/spotCheckMaterial.js.map +1 -1
  18. package/lib/index.js +173 -30
  19. package/lib/index.js.map +1 -1
  20. package/lib/methods/__tests__/contentHubMaterialGroup.test.d.ts +1 -0
  21. package/lib/methods/__tests__/contentHubMaterialGroup.test.js +97 -0
  22. package/lib/methods/__tests__/contentHubMaterialGroup.test.js.map +1 -0
  23. package/lib/methods/__tests__/contentHubMaterialGroupRule.test.d.ts +1 -0
  24. package/lib/methods/__tests__/contentHubMaterialGroupRule.test.js +168 -0
  25. package/lib/methods/__tests__/contentHubMaterialGroupRule.test.js.map +1 -0
  26. package/lib/methods/__tests__/contentHubPush.test.d.ts +1 -0
  27. package/lib/methods/__tests__/contentHubPush.test.js +568 -0
  28. package/lib/methods/__tests__/contentHubPush.test.js.map +1 -0
  29. package/lib/methods/__tests__/contentHubSyncRule.test.d.ts +1 -0
  30. package/lib/methods/__tests__/contentHubSyncRule.test.js +130 -0
  31. package/lib/methods/__tests__/contentHubSyncRule.test.js.map +1 -0
  32. package/lib/methods/__tests__/contentHubTagSync.test.d.ts +1 -0
  33. package/lib/methods/__tests__/contentHubTagSync.test.js +743 -0
  34. package/lib/methods/__tests__/contentHubTagSync.test.js.map +1 -0
  35. package/lib/methods/__tests__/tagLabel.test.d.ts +1 -0
  36. package/lib/methods/__tests__/tagLabel.test.js +136 -0
  37. package/lib/methods/__tests__/tagLabel.test.js.map +1 -0
  38. package/lib/methods/approval_engine.js +10 -7
  39. package/lib/methods/approval_engine.js.map +1 -1
  40. package/lib/methods/consts.d.ts +4 -0
  41. package/lib/methods/consts.js.map +1 -1
  42. package/lib/methods/content_hub.d.ts +8 -1
  43. package/lib/methods/content_hub.js +278 -27
  44. package/lib/methods/content_hub.js.map +1 -1
  45. package/lib/methods/content_hub_copyright.d.ts +15 -0
  46. package/lib/methods/content_hub_copyright.js +223 -0
  47. package/lib/methods/content_hub_copyright.js.map +1 -0
  48. package/lib/methods/content_hub_material_group.d.ts +29 -0
  49. package/lib/methods/content_hub_material_group.js +268 -0
  50. package/lib/methods/content_hub_material_group.js.map +1 -0
  51. package/lib/methods/content_hub_material_group_rule.d.ts +23 -0
  52. package/lib/methods/content_hub_material_group_rule.js +58 -0
  53. package/lib/methods/content_hub_material_group_rule.js.map +1 -0
  54. package/lib/methods/content_hub_sync_rule.d.ts +51 -0
  55. package/lib/methods/content_hub_sync_rule.js +122 -0
  56. package/lib/methods/content_hub_sync_rule.js.map +1 -0
  57. package/lib/methods/content_hub_tag.d.ts +57 -0
  58. package/lib/methods/content_hub_tag.js +830 -0
  59. package/lib/methods/content_hub_tag.js.map +1 -0
  60. package/lib/methods/pls.d.ts +4 -1
  61. package/lib/methods/pls.js +60 -51
  62. package/lib/methods/pls.js.map +1 -1
  63. package/lib/methods/tag_label.d.ts +19 -0
  64. package/lib/methods/tag_label.js +192 -0
  65. package/lib/methods/tag_label.js.map +1 -0
  66. package/main/default/applications/pepsico_content_setting.app.yml +8 -0
  67. package/main/default/objects/pepesio_ch__c/fields/media_type.field.yml +2 -0
  68. package/main/default/objects/pepesio_ch__c/fields/pepsico_material_approval.field.yml +6 -0
  69. package/main/default/objects/pepesio_ch__c/fields/push_status.field.yml +11 -0
  70. package/main/default/objects/pepesio_ch__c/fields/type.field.yml +48 -8
  71. package/main/default/objects/pepesio_ch__c/listviews/all.listview.yml +2 -0
  72. package/main/default/objects/pepsico_ch_material_group_rule/fields/active.field.yml +6 -0
  73. package/main/default/objects/pepsico_ch_material_group_rule/fields/classification.field.yml +73 -0
  74. package/main/default/objects/pepsico_ch_material_group_rule/fields/name.field.yml +9 -0
  75. package/main/default/objects/pepsico_ch_material_group_rule/fields/orgs.field.yml +10 -0
  76. package/main/default/objects/pepsico_ch_material_group_rule/fields/priority.field.yml +10 -0
  77. package/main/default/objects/pepsico_ch_material_group_rule/fields/query_mode.field.yml +12 -0
  78. package/main/default/objects/pepsico_ch_material_group_rule/fields/remark.field.yml +7 -0
  79. package/main/default/objects/pepsico_ch_material_group_rule/listviews/all.listview.yml +20 -0
  80. package/main/default/objects/pepsico_ch_material_group_rule/pepsico_ch_material_group_rule.object.yml +19 -0
  81. package/main/default/objects/pepsico_ch_material_group_rule/permissions/admin.permission.yml +15 -0
  82. package/main/default/objects/pepsico_ch_material_group_rule/permissions/user.permission.yml +14 -0
  83. package/main/default/objects/pepsico_ch_sync_rule/fields/active.field.yml +6 -0
  84. package/main/default/objects/pepsico_ch_sync_rule/fields/classification.field.yml +73 -0
  85. package/main/default/objects/pepsico_ch_sync_rule/fields/kol_classification.field.yml +67 -0
  86. package/main/default/objects/pepsico_ch_sync_rule/fields/name.field.yml +9 -0
  87. package/main/default/objects/pepsico_ch_sync_rule/fields/need_sync.field.yml +11 -0
  88. package/main/default/objects/pepsico_ch_sync_rule/fields/orgs.field.yml +10 -0
  89. package/main/default/objects/pepsico_ch_sync_rule/fields/priority.field.yml +10 -0
  90. package/main/default/objects/pepsico_ch_sync_rule/fields/product.field.yml +65 -0
  91. package/main/default/objects/pepsico_ch_sync_rule/fields/remark.field.yml +7 -0
  92. package/main/default/objects/pepsico_ch_sync_rule/fields/spot_check.field.yml +12 -0
  93. package/main/default/objects/pepsico_ch_sync_rule/listviews/all.listview.yml +24 -0
  94. package/main/default/objects/pepsico_ch_sync_rule/pepsico_ch_sync_rule.object.yml +19 -0
  95. package/main/default/objects/pepsico_ch_sync_rule/permissions/admin.permission.yml +15 -0
  96. package/main/default/objects/pepsico_ch_sync_rule/permissions/user.permission.yml +14 -0
  97. package/main/default/objects/pepsico_ch_tag_sync_log/fields/action.field.yml +6 -0
  98. package/main/default/objects/pepsico_ch_tag_sync_log/fields/changed_at.field.yml +6 -0
  99. package/main/default/objects/pepsico_ch_tag_sync_log/fields/duration.field.yml +6 -0
  100. package/main/default/objects/pepsico_ch_tag_sync_log/fields/error_code.field.yml +6 -0
  101. package/main/default/objects/pepsico_ch_tag_sync_log/fields/error_msg.field.yml +6 -0
  102. package/main/default/objects/pepsico_ch_tag_sync_log/fields/expired_date.field.yml +6 -0
  103. package/main/default/objects/pepsico_ch_tag_sync_log/fields/name.field.yml +7 -0
  104. package/main/default/objects/pepsico_ch_tag_sync_log/fields/parent_tag_val_id.field.yml +6 -0
  105. package/main/default/objects/pepsico_ch_tag_sync_log/fields/request_id.field.yml +7 -0
  106. package/main/default/objects/pepsico_ch_tag_sync_log/fields/result.field.yml +6 -0
  107. package/main/default/objects/pepsico_ch_tag_sync_log/fields/source_ip.field.yml +6 -0
  108. package/main/default/objects/pepsico_ch_tag_sync_log/fields/space.field.yml +6 -0
  109. package/main/default/objects/pepsico_ch_tag_sync_log/fields/tag_id.field.yml +6 -0
  110. package/main/default/objects/pepsico_ch_tag_sync_log/fields/tag_val_id.field.yml +7 -0
  111. package/main/default/objects/pepsico_ch_tag_sync_log/fields/tag_val_name.field.yml +6 -0
  112. package/main/default/objects/pepsico_ch_tag_sync_log/listviews/all.listview.yml +21 -0
  113. package/main/default/objects/pepsico_ch_tag_sync_log/pepsico_ch_tag_sync_log.object.yml +16 -0
  114. package/main/default/objects/pepsico_ch_tag_sync_log/permissions/admin.permission.yml +15 -0
  115. package/main/default/objects/pepsico_ch_tag_sync_log/permissions/user.permission.yml +14 -0
  116. package/main/default/objects/pepsico_ch_tag_sync_request/fields/changed_at.field.yml +6 -0
  117. package/main/default/objects/pepsico_ch_tag_sync_request/fields/duration.field.yml +6 -0
  118. package/main/default/objects/pepsico_ch_tag_sync_request/fields/expired_date.field.yml +6 -0
  119. package/main/default/objects/pepsico_ch_tag_sync_request/fields/item_count.field.yml +6 -0
  120. package/main/default/objects/pepsico_ch_tag_sync_request/fields/name.field.yml +7 -0
  121. package/main/default/objects/pepsico_ch_tag_sync_request/fields/request_id.field.yml +7 -0
  122. package/main/default/objects/pepsico_ch_tag_sync_request/fields/response.field.yml +9 -0
  123. package/main/default/objects/pepsico_ch_tag_sync_request/fields/result_code.field.yml +6 -0
  124. package/main/default/objects/pepsico_ch_tag_sync_request/fields/source_ip.field.yml +6 -0
  125. package/main/default/objects/pepsico_ch_tag_sync_request/fields/space.field.yml +6 -0
  126. package/main/default/objects/pepsico_ch_tag_sync_request/fields/tag_id.field.yml +6 -0
  127. package/main/default/objects/pepsico_ch_tag_sync_request/fields/tag_label.field.yml +6 -0
  128. package/main/default/objects/pepsico_ch_tag_sync_request/listviews/all.listview.yml +19 -0
  129. package/main/default/objects/pepsico_ch_tag_sync_request/pepsico_ch_tag_sync_request.object.yml +16 -0
  130. package/main/default/objects/pepsico_ch_tag_sync_request/permissions/admin.permission.yml +15 -0
  131. package/main/default/objects/pepsico_ch_tag_sync_request/permissions/user.permission.yml +14 -0
  132. package/main/default/objects/pepsico_material/fields/material_type_label.field.yml +10 -0
  133. package/main/default/objects/pepsico_material_approval/buttons/pepsico_confirm_online.button.yml +78 -1
  134. package/main/default/objects/pepsico_material_approval/buttons/pepsico_modify_pls.button.yml +1 -1
  135. package/main/default/objects/pepsico_material_approval/fields/available_channels.field.yml +2 -0
  136. package/main/default/objects/pepsico_material_approval/fields/available_channels_label.field.yml +10 -0
  137. package/main/default/objects/pepsico_material_approval/fields/ch_material_group.field.yml +56 -0
  138. package/main/default/objects/pepsico_material_approval/fields/ch_material_group_path.field.yml +14 -0
  139. package/main/default/objects/pepsico_material_approval/fields/ch_sync_enabled.field.yml +11 -0
  140. package/main/default/objects/pepsico_material_approval/fields/ch_sync_rule_check.field.yml +55 -0
  141. package/main/default/objects/pepsico_material_approval/fields/content_rule.field.yml +2 -0
  142. package/main/default/objects/pepsico_material_approval/fields/content_rule_label.field.yml +10 -0
  143. package/main/default/objects/pepsico_material_approval/fields/ec_check_food_name.field.yml +2 -2
  144. package/main/default/objects/pepsico_material_approval/fields/ec_check_ingredients.field.yml +2 -2
  145. package/main/default/objects/pepsico_material_approval/fields/ec_check_net_content.field.yml +2 -2
  146. package/main/default/objects/pepsico_material_approval/fields/ec_check_producer_address.field.yml +2 -2
  147. package/main/default/objects/pepsico_material_approval/fields/ec_check_producer_name.field.yml +2 -2
  148. package/main/default/objects/pepsico_material_approval/fields/ec_check_shelf_life.field.yml +2 -2
  149. package/main/default/objects/pepsico_material_approval/fields/ec_check_standard_code.field.yml +2 -2
  150. package/main/default/objects/pepsico_material_approval/fields/ec_check_storage_condition.field.yml +2 -2
  151. package/main/default/objects/pepsico_material_approval/fields/ec_has_detail_page.field.yml +1 -1
  152. package/main/default/objects/pepsico_material_approval/fields/ec_producers.field.yml +1 -1
  153. package/main/default/objects/pepsico_material_approval/fields/ec_qc_production_email.field.yml +2 -2
  154. package/main/default/objects/pepsico_material_approval/fields/ec_related_approval.field.yml +1 -1
  155. package/main/default/objects/pepsico_material_approval/fields/effective_date.field.yml +2 -0
  156. package/main/default/objects/pepsico_material_approval/fields/expiry_date.field.yml +2 -0
  157. package/main/default/objects/pepsico_material_approval/fields/form_version.field.yml +6 -2
  158. package/main/default/objects/pepsico_material_approval/fields/has_nutrition_table.field.yml +1 -1
  159. package/main/default/objects/pepsico_material_approval/fields/material_editable.field.yml +78 -0
  160. package/main/default/objects/pepsico_material_approval/fields/material_type.field.yml +2 -0
  161. package/main/default/objects/pepsico_material_approval/fields/material_type_label.field.yml +10 -0
  162. package/main/default/objects/pepsico_material_approval/fields/pls_file.field.yml +1 -1
  163. package/main/default/objects/pepsico_material_approval/fields/pls_km_files.field.yml +2 -5
  164. package/main/default/objects/pepsico_material_approval/fields/pls_upload_files.field.yml +1 -1
  165. package/main/default/objects/pepsico_material_approval/fields/product_label.field.yml +10 -0
  166. package/main/default/objects/pepsico_material_approval_data/fields/guding_pass.field.yml +9 -1
  167. package/main/default/objects/pepsico_material_data/fields/guding_pass.field.yml +9 -1
  168. package/main/default/objects/picklist_options/fields/ch_changed_at.field.yml +8 -0
  169. package/main/default/objects/picklist_options/picklist_options.object.yml +7 -0
  170. package/main/default/tabs/object_pepsico_ch_material_group_rule.tab.yml +8 -0
  171. package/main/default/tabs/object_pepsico_ch_sync_rule.tab.yml +8 -0
  172. package/main/default/triggers/pepsico_material_approval_tag_label.trigger.yml +80 -0
  173. package/main/default/triggers/pepsico_material_tag_label.trigger.yml +77 -0
  174. package/package.json +1 -1
  175. package/scripts/audit-legacy-tags.js +246 -0
  176. package/scripts/probe-content-hub-tags.js +134 -0
  177. package/main/default/objects/pepsico_material_approval/fields/has_detail_page.field.yml +0 -18
@@ -0,0 +1,743 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ var tslib_1 = require("tslib");
4
+ var content_hub_tag_1 = require("../content_hub_tag");
5
+ var PICKLIST_ID = 'pl-material-type';
6
+ var TAG_ID = '223321001114222';
7
+ var optionSeq = 0;
8
+ var createCtx = function (seedOptions, seedLogs) {
9
+ if (seedOptions === void 0) { seedOptions = []; }
10
+ if (seedLogs === void 0) { seedLogs = []; }
11
+ var options = seedOptions.map(function (option) { return (tslib_1.__assign({ enable: true, parent: null }, option)); });
12
+ var logs = seedLogs.slice();
13
+ var itemLogs = [];
14
+ var deletedLogIds = [];
15
+ var matchOption = function (option, filters) { return filters.every(function (_a) {
16
+ var _b = tslib_1.__read(_a, 3), field = _b[0], operator = _b[1], value = _b[2];
17
+ if (field === 'picklist') {
18
+ return PICKLIST_ID === value;
19
+ }
20
+ if (operator === 'in') {
21
+ return value.indexOf(option[field]) > -1;
22
+ }
23
+ return option[field] === value;
24
+ }); };
25
+ var ctx = {
26
+ meta: { user: { userId: 'integration-user', spaceId: 'space-1' } },
27
+ logger: { info: jest.fn(), warn: jest.fn(), error: jest.fn() },
28
+ options: options,
29
+ logs: logs,
30
+ itemLogs: itemLogs,
31
+ deletedLogIds: deletedLogIds,
32
+ getObject: function (objectName) {
33
+ if (objectName === 'picklists') {
34
+ return {
35
+ directFind: jest.fn().mockResolvedValue([{ _id: PICKLIST_ID, name: '素材类型', space: 'space-1' }])
36
+ };
37
+ }
38
+ if (objectName === 'picklist_options') {
39
+ return {
40
+ directFind: function (_a) {
41
+ var filters = _a.filters;
42
+ return tslib_1.__awaiter(void 0, void 0, void 0, function () { return tslib_1.__generator(this, function (_b) {
43
+ return [2, options.filter(function (option) { return matchOption(option, filters); })];
44
+ }); });
45
+ },
46
+ directInsert: function (doc) { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
47
+ var record;
48
+ return tslib_1.__generator(this, function (_a) {
49
+ record = tslib_1.__assign({ _id: "opt-".concat(++optionSeq) }, doc);
50
+ options.push(record);
51
+ return [2, record];
52
+ });
53
+ }); },
54
+ directUpdate: function (id, doc) { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
55
+ var record;
56
+ return tslib_1.__generator(this, function (_a) {
57
+ record = options.find(function (option) { return option._id === id; });
58
+ Object.assign(record, doc);
59
+ return [2, record];
60
+ });
61
+ }); }
62
+ };
63
+ }
64
+ if (objectName === 'pepsico_ch_tag_sync_log') {
65
+ return {
66
+ directFind: function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () { return tslib_1.__generator(this, function (_a) {
67
+ return [2, []];
68
+ }); }); },
69
+ directInsert: function (doc) { return tslib_1.__awaiter(void 0, void 0, void 0, function () { return tslib_1.__generator(this, function (_a) {
70
+ itemLogs.push(doc);
71
+ return [2, doc];
72
+ }); }); },
73
+ directDelete: function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () { return tslib_1.__generator(this, function (_a) {
74
+ return [2, undefined];
75
+ }); }); }
76
+ };
77
+ }
78
+ if (objectName === 'pepsico_ch_tag_sync_request') {
79
+ return {
80
+ directFind: function (_a) {
81
+ var filters = _a.filters;
82
+ return tslib_1.__awaiter(void 0, void 0, void 0, function () {
83
+ var _b, field, operator, value;
84
+ return tslib_1.__generator(this, function (_c) {
85
+ _b = tslib_1.__read(filters[0], 3), field = _b[0], operator = _b[1], value = _b[2];
86
+ if (operator === '<') {
87
+ return [2, logs.filter(function (log) { return log[field] && log[field] < value; })];
88
+ }
89
+ return [2, logs.filter(function (log) { return log[field] === value; })];
90
+ });
91
+ });
92
+ },
93
+ directInsert: function (doc) { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
94
+ var record;
95
+ return tslib_1.__generator(this, function (_a) {
96
+ record = tslib_1.__assign({ _id: "log-".concat(logs.length + 1) }, doc);
97
+ logs.push(record);
98
+ return [2, record];
99
+ });
100
+ }); },
101
+ directDelete: function (id) { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
102
+ var index;
103
+ return tslib_1.__generator(this, function (_a) {
104
+ deletedLogIds.push(id);
105
+ index = logs.findIndex(function (log) { return log._id === id; });
106
+ logs.splice(index, 1);
107
+ return [2];
108
+ });
109
+ }); }
110
+ };
111
+ }
112
+ throw new Error("Unexpected object: ".concat(objectName));
113
+ }
114
+ };
115
+ return ctx;
116
+ };
117
+ var request = function (overrides) {
118
+ if (overrides === void 0) { overrides = {}; }
119
+ return (tslib_1.__assign({ requestId: 'ch-20260908-000123', changedAt: '2026-09-08T15:24:33+08:00', tagId: TAG_ID, items: [{ action: 'upsert', tagValId: '2233305175173759', name: '品牌 Social 图片' }] }, overrides));
120
+ };
121
+ var expectRejected = function (ctx, params, code) { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
122
+ return tslib_1.__generator(this, function (_a) {
123
+ switch (_a.label) {
124
+ case 0: return [4, expect((0, content_hub_tag_1.syncTagValues)(ctx, params)).rejects.toMatchObject({ code: code })];
125
+ case 1:
126
+ _a.sent();
127
+ return [2];
128
+ }
129
+ });
130
+ }); };
131
+ describe('Content Hub 标签同步 - 批次校验', function () {
132
+ it('拒绝不在托管白名单的 tagId', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
133
+ return tslib_1.__generator(this, function (_a) {
134
+ switch (_a.label) {
135
+ case 0: return [4, expectRejected(createCtx(), request({ tagId: '999999' }), content_hub_tag_1.TagSyncCode.TAG_NOT_ALLOWED)];
136
+ case 1:
137
+ _a.sent();
138
+ return [2];
139
+ }
140
+ });
141
+ }); });
142
+ it('拒绝缺少时区偏移的 changedAt', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
143
+ return tslib_1.__generator(this, function (_a) {
144
+ switch (_a.label) {
145
+ case 0: return [4, expectRejected(createCtx(), request({ changedAt: '2026-09-08T15:24:33' }), content_hub_tag_1.TagSyncCode.BAD_REQUEST)];
146
+ case 1:
147
+ _a.sent();
148
+ return [2];
149
+ }
150
+ });
151
+ }); });
152
+ it('拒绝缺失的 requestId', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
153
+ return tslib_1.__generator(this, function (_a) {
154
+ switch (_a.label) {
155
+ case 0: return [4, expectRejected(createCtx(), request({ requestId: ' ' }), content_hub_tag_1.TagSyncCode.BAD_REQUEST)];
156
+ case 1:
157
+ _a.sent();
158
+ return [2];
159
+ }
160
+ });
161
+ }); });
162
+ it('请求体整个为空时提示 Content-Type, 而不是误导成 requestId 漏填', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
163
+ return tslib_1.__generator(this, function (_a) {
164
+ switch (_a.label) {
165
+ case 0: return [4, expect((0, content_hub_tag_1.syncTagValues)(createCtx(), {})).rejects.toMatchObject({
166
+ code: content_hub_tag_1.TagSyncCode.BAD_REQUEST,
167
+ message: expect.stringContaining('Content-Type')
168
+ })];
169
+ case 1:
170
+ _a.sent();
171
+ return [2];
172
+ }
173
+ });
174
+ }); });
175
+ it('只漏了 requestId 时仍然报 requestId, 不误报成 Content-Type', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
176
+ var params;
177
+ return tslib_1.__generator(this, function (_a) {
178
+ switch (_a.label) {
179
+ case 0:
180
+ params = request();
181
+ delete params.requestId;
182
+ return [4, expect((0, content_hub_tag_1.syncTagValues)(createCtx(), params)).rejects.toMatchObject({
183
+ code: content_hub_tag_1.TagSyncCode.BAD_REQUEST,
184
+ message: expect.stringContaining('requestId')
185
+ })];
186
+ case 1:
187
+ _a.sent();
188
+ return [2];
189
+ }
190
+ });
191
+ }); });
192
+ it('拒绝超过 100 条的批次', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
193
+ var items;
194
+ return tslib_1.__generator(this, function (_a) {
195
+ switch (_a.label) {
196
+ case 0:
197
+ items = _range(content_hub_tag_1.MAX_ITEMS_PER_BATCH + 1).map(function (index) { return ({ tagValId: "v".concat(index), name: "\u540D\u79F0".concat(index) }); });
198
+ return [4, expectRejected(createCtx(), request({ items: items }), content_hub_tag_1.TagSyncCode.TOO_MANY_ITEMS)];
199
+ case 1:
200
+ _a.sent();
201
+ return [2];
202
+ }
203
+ });
204
+ }); });
205
+ it('拒绝同批次内重复的 tagValId —— 避免既 disable 又 enable 的自相矛盾指令', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
206
+ var items;
207
+ return tslib_1.__generator(this, function (_a) {
208
+ switch (_a.label) {
209
+ case 0:
210
+ items = [
211
+ { action: 'disable', tagValId: 'v1' },
212
+ { action: 'enable', tagValId: 'v1' }
213
+ ];
214
+ return [4, expectRejected(createCtx([{ _id: 'o1', value: 'v1', name: 'V1' }]), request({ items: items }), content_hub_tag_1.TagSyncCode.BAD_REQUEST)];
215
+ case 1:
216
+ _a.sent();
217
+ return [2];
218
+ }
219
+ });
220
+ }); });
221
+ it('拒绝含连字符的 tagValId', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
222
+ return tslib_1.__generator(this, function (_a) {
223
+ switch (_a.label) {
224
+ case 0: return [4, expectRejected(createCtx(), request({ items: [{ tagValId: 'a-b', name: 'X' }] }), content_hub_tag_1.TagSyncCode.INVALID_TAG_VAL_ID)];
225
+ case 1:
226
+ _a.sent();
227
+ return [2];
228
+ }
229
+ });
230
+ }); });
231
+ it('拒绝 upsert 缺少 name', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
232
+ return tslib_1.__generator(this, function (_a) {
233
+ switch (_a.label) {
234
+ case 0: return [4, expectRejected(createCtx(), request({ items: [{ tagValId: 'v1' }] }), content_hub_tag_1.TagSyncCode.BAD_REQUEST)];
235
+ case 1:
236
+ _a.sent();
237
+ return [2];
238
+ }
239
+ });
240
+ }); });
241
+ it('拒绝非法的 action', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
242
+ return tslib_1.__generator(this, function (_a) {
243
+ switch (_a.label) {
244
+ case 0: return [4, expectRejected(createCtx(), request({ items: [{ action: 'constructor', tagValId: 'v1' }] }), content_hub_tag_1.TagSyncCode.BAD_REQUEST)];
245
+ case 1:
246
+ _a.sent();
247
+ return [2];
248
+ }
249
+ });
250
+ }); });
251
+ it('拒绝不存在的 parentTagValId', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
252
+ var items;
253
+ return tslib_1.__generator(this, function (_a) {
254
+ switch (_a.label) {
255
+ case 0:
256
+ items = [{ tagValId: 'v1', name: 'V1', parentTagValId: 'missing' }];
257
+ return [4, expectRejected(createCtx(), request({ items: items }), content_hub_tag_1.TagSyncCode.PARENT_NOT_FOUND)];
258
+ case 1:
259
+ _a.sent();
260
+ return [2];
261
+ }
262
+ });
263
+ }); });
264
+ it('拒绝对已存在记录做跨父级移动', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
265
+ var ctx, items;
266
+ return tslib_1.__generator(this, function (_a) {
267
+ switch (_a.label) {
268
+ case 0:
269
+ ctx = createCtx([
270
+ { _id: 'o-parent', value: 'p1', name: '父级' },
271
+ { _id: 'o-child', value: 'v1', name: 'V1', parent: null }
272
+ ]);
273
+ items = [{ tagValId: 'v1', name: 'V1', parentTagValId: 'p1' }];
274
+ return [4, expectRejected(ctx, request({ items: items }), content_hub_tag_1.TagSyncCode.PARENT_IMMUTABLE)];
275
+ case 1:
276
+ _a.sent();
277
+ return [2];
278
+ }
279
+ });
280
+ }); });
281
+ it('整批被拒时一条都不写库', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
282
+ var ctx;
283
+ return tslib_1.__generator(this, function (_a) {
284
+ switch (_a.label) {
285
+ case 0:
286
+ ctx = createCtx();
287
+ return [4, expectRejected(ctx, request({ tagId: '999999' }), content_hub_tag_1.TagSyncCode.TAG_NOT_ALLOWED)];
288
+ case 1:
289
+ _a.sent();
290
+ expect(ctx.options).toHaveLength(0);
291
+ expect(ctx.logs).toHaveLength(0);
292
+ return [2];
293
+ }
294
+ });
295
+ }); });
296
+ });
297
+ describe('Content Hub 标签同步 - upsert', function () {
298
+ it('新建标签值, 启用并留空 sort_no 让它排在下拉末尾', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
299
+ var ctx, result, created;
300
+ return tslib_1.__generator(this, function (_a) {
301
+ switch (_a.label) {
302
+ case 0:
303
+ ctx = createCtx();
304
+ return [4, (0, content_hub_tag_1.syncTagValues)(ctx, request())];
305
+ case 1:
306
+ result = _a.sent();
307
+ expect(result.code).toBe(content_hub_tag_1.TagSyncCode.OK);
308
+ expect(result.data.results).toEqual([{ tagValId: '2233305175173759', result: 'created' }]);
309
+ created = ctx.options[0];
310
+ expect(created).toMatchObject({
311
+ picklist: PICKLIST_ID,
312
+ value: '2233305175173759',
313
+ name: '品牌 Social 图片',
314
+ enable: true,
315
+ parent: null
316
+ });
317
+ expect(created.sort_no).toBeUndefined();
318
+ return [2];
319
+ }
320
+ });
321
+ }); });
322
+ it('已存在记录改名返回 updated, 同名返回 unchanged', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
323
+ var ctx, updated, unchanged;
324
+ return tslib_1.__generator(this, function (_a) {
325
+ switch (_a.label) {
326
+ case 0:
327
+ ctx = createCtx([{ _id: 'o1', value: 'v1', name: '旧名称' }]);
328
+ return [4, (0, content_hub_tag_1.syncTagValues)(ctx, request({ items: [{ tagValId: 'v1', name: '新名称' }] }))];
329
+ case 1:
330
+ updated = _a.sent();
331
+ expect(updated.data.results[0]).toEqual({ tagValId: 'v1', result: 'updated' });
332
+ expect(ctx.options[0].name).toBe('新名称');
333
+ return [4, (0, content_hub_tag_1.syncTagValues)(ctx, request({
334
+ requestId: 'ch-2',
335
+ changedAt: '2026-09-08T16:24:33+08:00',
336
+ items: [{ tagValId: 'v1', name: '新名称' }]
337
+ }))];
338
+ case 2:
339
+ unchanged = _a.sent();
340
+ expect(unchanged.data.results[0]).toEqual({ tagValId: 'v1', result: 'unchanged' });
341
+ return [2];
342
+ }
343
+ });
344
+ }); });
345
+ it('改名不会动启用状态', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
346
+ var ctx;
347
+ return tslib_1.__generator(this, function (_a) {
348
+ switch (_a.label) {
349
+ case 0:
350
+ ctx = createCtx([{ _id: 'o1', value: 'v1', name: '旧名称', enable: false }]);
351
+ return [4, (0, content_hub_tag_1.syncTagValues)(ctx, request({ items: [{ tagValId: 'v1', name: '新名称' }] }))];
352
+ case 1:
353
+ _a.sent();
354
+ expect(ctx.options[0].enable).toBe(false);
355
+ return [2];
356
+ }
357
+ });
358
+ }); });
359
+ it('同一批次内先建父级再建子级', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
360
+ var ctx, items, result, parent, child;
361
+ return tslib_1.__generator(this, function (_a) {
362
+ switch (_a.label) {
363
+ case 0:
364
+ ctx = createCtx();
365
+ items = [
366
+ { tagValId: 'child', name: '子级', parentTagValId: 'parent' },
367
+ { tagValId: 'parent', name: '父级' }
368
+ ];
369
+ return [4, (0, content_hub_tag_1.syncTagValues)(ctx, request({ items: items }))];
370
+ case 1:
371
+ result = _a.sent();
372
+ expect(result.data.results).toEqual([
373
+ { tagValId: 'child', result: 'created' },
374
+ { tagValId: 'parent', result: 'created' }
375
+ ]);
376
+ parent = ctx.options.find(function (option) { return option.value === 'parent'; });
377
+ child = ctx.options.find(function (option) { return option.value === 'child'; });
378
+ expect(child.parent).toBe(parent._id);
379
+ return [2];
380
+ }
381
+ });
382
+ }); });
383
+ it('已存在记录重复传相同父级不算跨父级移动', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
384
+ var ctx, result;
385
+ return tslib_1.__generator(this, function (_a) {
386
+ switch (_a.label) {
387
+ case 0:
388
+ ctx = createCtx([
389
+ { _id: 'o-parent', value: 'p1', name: '父级' },
390
+ { _id: 'o-child', value: 'v1', name: 'V1', parent: 'o-parent' }
391
+ ]);
392
+ return [4, (0, content_hub_tag_1.syncTagValues)(ctx, request({ items: [{ tagValId: 'v1', name: 'V2', parentTagValId: 'p1' }] }))];
393
+ case 1:
394
+ result = _a.sent();
395
+ expect(result.data.results[0]).toEqual({ tagValId: 'v1', result: 'updated' });
396
+ return [2];
397
+ }
398
+ });
399
+ }); });
400
+ });
401
+ describe('Content Hub 标签同步 - 停用与恢复', function () {
402
+ it('停用与恢复各自独立, 目标已是该状态时返回 unchanged', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
403
+ var ctx, disabled, again, enabled;
404
+ return tslib_1.__generator(this, function (_a) {
405
+ switch (_a.label) {
406
+ case 0:
407
+ ctx = createCtx([{ _id: 'o1', value: 'v1', name: 'V1', enable: true }]);
408
+ return [4, (0, content_hub_tag_1.syncTagValues)(ctx, request({ items: [{ action: 'disable', tagValId: 'v1' }] }))];
409
+ case 1:
410
+ disabled = _a.sent();
411
+ expect(disabled.data.results[0]).toEqual({ tagValId: 'v1', result: 'disabled' });
412
+ expect(ctx.options[0].enable).toBe(false);
413
+ return [4, (0, content_hub_tag_1.syncTagValues)(ctx, request({
414
+ requestId: 'ch-2',
415
+ changedAt: '2026-09-08T16:00:00+08:00',
416
+ items: [{ action: 'disable', tagValId: 'v1' }]
417
+ }))];
418
+ case 2:
419
+ again = _a.sent();
420
+ expect(again.data.results[0]).toEqual({ tagValId: 'v1', result: 'unchanged' });
421
+ return [4, (0, content_hub_tag_1.syncTagValues)(ctx, request({
422
+ requestId: 'ch-3',
423
+ changedAt: '2026-09-08T17:00:00+08:00',
424
+ items: [{ action: 'enable', tagValId: 'v1' }]
425
+ }))];
426
+ case 3:
427
+ enabled = _a.sent();
428
+ expect(enabled.data.results[0]).toEqual({ tagValId: 'v1', result: 'enabled' });
429
+ expect(ctx.options[0].enable).toBe(true);
430
+ return [2];
431
+ }
432
+ });
433
+ }); });
434
+ it('停用保留记录, 不级联子节点', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
435
+ var ctx;
436
+ return tslib_1.__generator(this, function (_a) {
437
+ switch (_a.label) {
438
+ case 0:
439
+ ctx = createCtx([
440
+ { _id: 'o-parent', value: 'p1', name: '父级' },
441
+ { _id: 'o-child', value: 'c1', name: '子级', parent: 'o-parent' }
442
+ ]);
443
+ return [4, (0, content_hub_tag_1.syncTagValues)(ctx, request({ items: [{ action: 'disable', tagValId: 'p1' }] }))];
444
+ case 1:
445
+ _a.sent();
446
+ expect(ctx.options).toHaveLength(2);
447
+ expect(ctx.options.find(function (option) { return option.value === 'c1'; }).enable).toBe(true);
448
+ return [2];
449
+ }
450
+ });
451
+ }); });
452
+ it('停用不存在的 tagValId 只失败该条, 整批返回 1001', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
453
+ var ctx, items, result;
454
+ return tslib_1.__generator(this, function (_a) {
455
+ switch (_a.label) {
456
+ case 0:
457
+ ctx = createCtx([{ _id: 'o1', value: 'v1', name: 'V1' }]);
458
+ items = [
459
+ { action: 'disable', tagValId: 'v1' },
460
+ { action: 'enable', tagValId: 'missing' }
461
+ ];
462
+ return [4, (0, content_hub_tag_1.syncTagValues)(ctx, request({ items: items }))];
463
+ case 1:
464
+ result = _a.sent();
465
+ expect(result.code).toBe(content_hub_tag_1.TagSyncCode.PARTIAL);
466
+ expect(result.data.results).toEqual([
467
+ { tagValId: 'v1', result: 'disabled' },
468
+ { tagValId: 'missing', result: 'failed', code: content_hub_tag_1.TagSyncCode.TAG_VAL_NOT_FOUND, msg: 'tagValId 不存在' }
469
+ ]);
470
+ expect(ctx.options[0].enable).toBe(false);
471
+ return [2];
472
+ }
473
+ });
474
+ }); });
475
+ });
476
+ describe('Content Hub 标签同步 - 过期事件与幂等', function () {
477
+ it('丢弃早于已处理记录的事件', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
478
+ var ctx, stale;
479
+ return tslib_1.__generator(this, function (_a) {
480
+ switch (_a.label) {
481
+ case 0:
482
+ ctx = createCtx([{ _id: 'o1', value: 'v1', name: '新名称' }]);
483
+ return [4, (0, content_hub_tag_1.syncTagValues)(ctx, request({ items: [{ tagValId: 'v1', name: '新名称' }] }))];
484
+ case 1:
485
+ _a.sent();
486
+ return [4, (0, content_hub_tag_1.syncTagValues)(ctx, request({
487
+ requestId: 'ch-earlier',
488
+ changedAt: '2026-09-08T10:00:00+08:00',
489
+ items: [{ tagValId: 'v1', name: '更早的名称' }]
490
+ }))];
491
+ case 2:
492
+ stale = _a.sent();
493
+ expect(stale.code).toBe(content_hub_tag_1.TagSyncCode.OK);
494
+ expect(stale.data.results[0]).toEqual({ tagValId: 'v1', result: 'stale' });
495
+ expect(ctx.options[0].name).toBe('新名称');
496
+ return [2];
497
+ }
498
+ });
499
+ }); });
500
+ it('按条 changedAt 覆盖批次值参与过期判断', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
501
+ var ctx, result;
502
+ return tslib_1.__generator(this, function (_a) {
503
+ switch (_a.label) {
504
+ case 0:
505
+ ctx = createCtx([{ _id: 'o1', value: 'v1', name: 'V1', ch_changed_at: new Date('2026-09-08T12:00:00+08:00') }]);
506
+ return [4, (0, content_hub_tag_1.syncTagValues)(ctx, request({
507
+ changedAt: '2026-09-08T20:00:00+08:00',
508
+ items: [{ tagValId: 'v1', name: 'V2', changedAt: '2026-09-08T08:00:00+08:00' }]
509
+ }))];
510
+ case 1:
511
+ result = _a.sent();
512
+ expect(result.data.results[0]).toEqual({ tagValId: 'v1', result: 'stale' });
513
+ return [2];
514
+ }
515
+ });
516
+ }); });
517
+ it('7 天内重复的 requestId 返回首次结果且不重复写库', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
518
+ var ctx, first, second;
519
+ return tslib_1.__generator(this, function (_a) {
520
+ switch (_a.label) {
521
+ case 0:
522
+ ctx = createCtx();
523
+ return [4, (0, content_hub_tag_1.syncTagValues)(ctx, request())];
524
+ case 1:
525
+ first = _a.sent();
526
+ expect(first.data.idempotent).toBe(false);
527
+ return [4, (0, content_hub_tag_1.syncTagValues)(ctx, request({ items: [{ tagValId: '2233305175173759', name: '改了个名字' }] }))];
528
+ case 2:
529
+ second = _a.sent();
530
+ expect(second.data.idempotent).toBe(true);
531
+ expect(second.data.results).toEqual(first.data.results);
532
+ expect(ctx.options).toHaveLength(1);
533
+ expect(ctx.options[0].name).toBe('品牌 Social 图片');
534
+ expect(ctx.logs).toHaveLength(1);
535
+ return [2];
536
+ }
537
+ });
538
+ }); });
539
+ it('幂等命中时保留首次的部分成功语义', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
540
+ var ctx, items, first, second;
541
+ return tslib_1.__generator(this, function (_a) {
542
+ switch (_a.label) {
543
+ case 0:
544
+ ctx = createCtx();
545
+ items = [{ action: 'disable', tagValId: 'missing' }];
546
+ return [4, (0, content_hub_tag_1.syncTagValues)(ctx, request({ items: items }))];
547
+ case 1:
548
+ first = _a.sent();
549
+ expect(first.code).toBe(content_hub_tag_1.TagSyncCode.PARTIAL);
550
+ return [4, (0, content_hub_tag_1.syncTagValues)(ctx, request({ items: items }))];
551
+ case 2:
552
+ second = _a.sent();
553
+ expect(second.code).toBe(content_hub_tag_1.TagSyncCode.PARTIAL);
554
+ expect(second.data.idempotent).toBe(true);
555
+ return [2];
556
+ }
557
+ });
558
+ }); });
559
+ it('顺带清理已过期的日志', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
560
+ var ctx;
561
+ return tslib_1.__generator(this, function (_a) {
562
+ switch (_a.label) {
563
+ case 0:
564
+ ctx = createCtx([], [{ _id: 'log-old', request_id: 'ch-old', expired_date: new Date('2026-09-01T00:00:00+08:00') }]);
565
+ return [4, (0, content_hub_tag_1.syncTagValues)(ctx, request())];
566
+ case 1:
567
+ _a.sent();
568
+ expect(ctx.deletedLogIds).toEqual(['log-old']);
569
+ return [2];
570
+ }
571
+ });
572
+ }); });
573
+ });
574
+ describe('Content Hub 标签同步 - issue #448 实现要点', function () {
575
+ it('tagValId 只允许字母数字下划线(C2)', function () {
576
+ expect((0, content_hub_tag_1.isValidTagValId)('223321001114381')).toBe(true);
577
+ expect((0, content_hub_tag_1.isValidTagValId)('brand_social_01')).toBe(true);
578
+ expect((0, content_hub_tag_1.isValidTagValId)('a-b')).toBe(false);
579
+ expect((0, content_hub_tag_1.isValidTagValId)('a.b')).toBe(false);
580
+ expect((0, content_hub_tag_1.isValidTagValId)('a b')).toBe(false);
581
+ expect((0, content_hub_tag_1.isValidTagValId)('中文值')).toBe(false);
582
+ expect((0, content_hub_tag_1.isValidTagValId)('9'.repeat(65))).toBe(false);
583
+ expect((0, content_hub_tag_1.isValidTagValId)('9'.repeat(64))).toBe(true);
584
+ });
585
+ it('层级超过 3 层整批拒绝(C5)', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
586
+ var ctx;
587
+ return tslib_1.__generator(this, function (_a) {
588
+ switch (_a.label) {
589
+ case 0:
590
+ ctx = createCtx([
591
+ { _id: 'o1', value: 'lv1', name: '一层' },
592
+ { _id: 'o2', value: 'lv2', name: '二层', parent: 'o1' },
593
+ { _id: 'o3', value: 'lv3', name: '三层', parent: 'o2' }
594
+ ]);
595
+ return [4, expectRejected(ctx, request({
596
+ items: [{ tagValId: 'lv4', name: '四层', parentTagValId: 'lv3' }]
597
+ }), content_hub_tag_1.TagSyncCode.BAD_REQUEST)];
598
+ case 1:
599
+ _a.sent();
600
+ expect(ctx.options).toHaveLength(3);
601
+ return [2];
602
+ }
603
+ });
604
+ }); });
605
+ it('正好 3 层可以建', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
606
+ var ctx, result;
607
+ return tslib_1.__generator(this, function (_a) {
608
+ switch (_a.label) {
609
+ case 0:
610
+ ctx = createCtx([
611
+ { _id: 'o1', value: 'lv1', name: '一层' },
612
+ { _id: 'o2', value: 'lv2', name: '二层', parent: 'o1' }
613
+ ]);
614
+ return [4, (0, content_hub_tag_1.syncTagValues)(ctx, request({
615
+ items: [{ tagValId: 'lv3', name: '三层', parentTagValId: 'lv2' }]
616
+ }))];
617
+ case 1:
618
+ result = _a.sent();
619
+ expect(result.data.results[0]).toEqual({ tagValId: 'lv3', result: 'created' });
620
+ return [2];
621
+ }
622
+ });
623
+ }); });
624
+ it('同批次建出的第 4 层同样被拒(父链一半在库里一半在批次里)', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
625
+ var ctx;
626
+ return tslib_1.__generator(this, function (_a) {
627
+ switch (_a.label) {
628
+ case 0:
629
+ ctx = createCtx([{ _id: 'o1', value: 'lv1', name: '一层' }]);
630
+ return [4, expectRejected(ctx, request({
631
+ items: [
632
+ { tagValId: 'lv2', name: '二层', parentTagValId: 'lv1' },
633
+ { tagValId: 'lv3', name: '三层', parentTagValId: 'lv2' },
634
+ { tagValId: 'lv4', name: '四层', parentTagValId: 'lv3' }
635
+ ]
636
+ }), content_hub_tag_1.TagSyncCode.BAD_REQUEST)];
637
+ case 1:
638
+ _a.sent();
639
+ expect(ctx.options).toHaveLength(1);
640
+ return [2];
641
+ }
642
+ });
643
+ }); });
644
+ it('契约外字段一律忽略,不报错', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
645
+ var ctx, result;
646
+ return tslib_1.__generator(this, function (_a) {
647
+ switch (_a.label) {
648
+ case 0:
649
+ ctx = createCtx();
650
+ return [4, (0, content_hub_tag_1.syncTagValues)(ctx, request({
651
+ items: [{ tagValId: 'extra01', name: '带多余字段', sortNo: 99, color: '#fff', default: true, space: 'hack' }]
652
+ }))];
653
+ case 1:
654
+ result = _a.sent();
655
+ expect(result.data.results[0]).toEqual({ tagValId: 'extra01', result: 'created' });
656
+ expect(ctx.options[0].sort_no).toBeUndefined();
657
+ expect(ctx.options[0].space).toBe('space-1');
658
+ return [2];
659
+ }
660
+ });
661
+ }); });
662
+ it('明细日志一条 item 一行,带动作与结果', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
663
+ var ctx;
664
+ return tslib_1.__generator(this, function (_a) {
665
+ switch (_a.label) {
666
+ case 0:
667
+ ctx = createCtx([{ _id: 'o1', value: 'v1', name: 'V1' }]);
668
+ return [4, (0, content_hub_tag_1.syncTagValues)(ctx, request({
669
+ items: [
670
+ { tagValId: 'v1', name: 'V2' },
671
+ { action: 'disable', tagValId: 'missing01' }
672
+ ]
673
+ }))];
674
+ case 1:
675
+ _a.sent();
676
+ expect(ctx.itemLogs).toHaveLength(2);
677
+ expect(ctx.itemLogs[0]).toMatchObject({ tag_val_id: 'v1', action: 'upsert', result: 'updated', tag_val_name: 'V2' });
678
+ expect(ctx.itemLogs[1]).toMatchObject({ tag_val_id: 'missing01', action: 'disable', result: 'failed', error_code: content_hub_tag_1.TagSyncCode.TAG_VAL_NOT_FOUND });
679
+ return [2];
680
+ }
681
+ });
682
+ }); });
683
+ it('同一 tagId 的批次串行处理,不交叉', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
684
+ var ctx, order, wrap;
685
+ return tslib_1.__generator(this, function (_a) {
686
+ switch (_a.label) {
687
+ case 0:
688
+ ctx = createCtx();
689
+ order = [];
690
+ wrap = function (label, items) { return (0, content_hub_tag_1.syncTagValues)(ctx, request({ requestId: label, items: items }))
691
+ .then(function () { return order.push(label); }); };
692
+ return [4, Promise.all([
693
+ wrap('batch-a', [{ tagValId: 'sa', name: 'A' }]),
694
+ wrap('batch-b', [{ tagValId: 'sb', name: 'B' }])
695
+ ])];
696
+ case 1:
697
+ _a.sent();
698
+ expect(order).toEqual(['batch-a', 'batch-b']);
699
+ expect(ctx.options).toHaveLength(2);
700
+ return [2];
701
+ }
702
+ });
703
+ }); });
704
+ it('前一个批次抛错不会卡住同 tagId 的后续批次', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
705
+ var ctx, after;
706
+ return tslib_1.__generator(this, function (_a) {
707
+ switch (_a.label) {
708
+ case 0:
709
+ ctx = createCtx();
710
+ return [4, expect((0, content_hub_tag_1.syncTagValues)(ctx, request({ requestId: 'bad', items: [{ tagValId: 'x', name: 'X', parentTagValId: 'nope' }] })))
711
+ .rejects.toMatchObject({ code: content_hub_tag_1.TagSyncCode.PARENT_NOT_FOUND })];
712
+ case 1:
713
+ _a.sent();
714
+ return [4, (0, content_hub_tag_1.syncTagValues)(ctx, request({ requestId: 'good', items: [{ tagValId: 'y', name: 'Y' }] }))];
715
+ case 2:
716
+ after = _a.sent();
717
+ expect(after.data.results[0]).toEqual({ tagValId: 'y', result: 'created' });
718
+ return [2];
719
+ }
720
+ });
721
+ }); });
722
+ });
723
+ describe('Content Hub 标签同步 - 辅助函数', function () {
724
+ it('业务 code 映射到约定的 HTTP 状态码', function () {
725
+ expect((0, content_hub_tag_1.httpStatusOf)(content_hub_tag_1.TagSyncCode.OK)).toBe(200);
726
+ expect((0, content_hub_tag_1.httpStatusOf)(content_hub_tag_1.TagSyncCode.PARTIAL)).toBe(200);
727
+ expect((0, content_hub_tag_1.httpStatusOf)(content_hub_tag_1.TagSyncCode.BAD_REQUEST)).toBe(400);
728
+ expect((0, content_hub_tag_1.httpStatusOf)(content_hub_tag_1.TagSyncCode.TAG_VAL_NOT_FOUND)).toBe(400);
729
+ expect((0, content_hub_tag_1.httpStatusOf)(content_hub_tag_1.TagSyncCode.UNAUTHORIZED)).toBe(401);
730
+ expect((0, content_hub_tag_1.httpStatusOf)(content_hub_tag_1.TagSyncCode.SERVER_ERROR)).toBe(500);
731
+ });
732
+ it('父子互相引用时排序不会死循环', function () {
733
+ var items = [
734
+ { index: 0, action: 'upsert', tagValId: 'a', parentTagValId: 'b' },
735
+ { index: 1, action: 'upsert', tagValId: 'b', parentTagValId: 'a' }
736
+ ];
737
+ expect((0, content_hub_tag_1.orderItemsByParent)(items)).toHaveLength(2);
738
+ });
739
+ });
740
+ function _range(count) {
741
+ return Array.from({ length: count }, function (_value, index) { return index; });
742
+ }
743
+ //# sourceMappingURL=contentHubTagSync.test.js.map