@nextsparkjs/theme-default 0.1.0-beta.20 → 0.1.0-beta.22

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 (221) hide show
  1. package/package.json +1 -1
  2. package/tests/cypress/e2e/_devtools/access.bdd.md +262 -0
  3. package/tests/cypress/e2e/_devtools/access.cy.ts +171 -0
  4. package/tests/cypress/e2e/_devtools/navigation.bdd.md +261 -0
  5. package/tests/cypress/e2e/_devtools/navigation.cy.ts +157 -0
  6. package/tests/cypress/e2e/_devtools/pages.bdd.md +303 -0
  7. package/tests/cypress/e2e/_devtools/pages.cy.ts +184 -0
  8. package/tests/cypress/e2e/_docs/README.md +215 -0
  9. package/tests/cypress/e2e/_docs/tutorials/sector7-superadmin-teams.narration.json +155 -0
  10. package/tests/cypress/e2e/_docs/tutorials/sector7-superadmin.cy.ts +390 -0
  11. package/tests/cypress/e2e/_docs/tutorials/teams-system.doc.cy.ts +349 -0
  12. package/tests/cypress/e2e/_docs/tutorials/teams-system.narration.json +165 -0
  13. package/tests/cypress/e2e/_selectors/auth.cy.ts +306 -0
  14. package/tests/cypress/e2e/_selectors/billing.cy.ts +89 -0
  15. package/tests/cypress/e2e/_selectors/dashboard-mobile.cy.ts +113 -0
  16. package/tests/cypress/e2e/_selectors/dashboard-navigation.cy.ts +89 -0
  17. package/tests/cypress/e2e/_selectors/dashboard-sidebar.cy.ts +60 -0
  18. package/tests/cypress/e2e/_selectors/dashboard-topnav.cy.ts +146 -0
  19. package/tests/cypress/e2e/_selectors/devtools.cy.ts +210 -0
  20. package/tests/cypress/e2e/_selectors/global-search.cy.ts +88 -0
  21. package/tests/cypress/e2e/_selectors/pages-editor.cy.ts +179 -0
  22. package/tests/cypress/e2e/_selectors/posts-editor.cy.ts +282 -0
  23. package/tests/cypress/e2e/_selectors/public.cy.ts +112 -0
  24. package/tests/cypress/e2e/_selectors/settings-api-keys.cy.ts +228 -0
  25. package/tests/cypress/e2e/_selectors/settings-billing.cy.ts +105 -0
  26. package/tests/cypress/e2e/_selectors/settings-layout.cy.ts +119 -0
  27. package/tests/cypress/e2e/_selectors/settings-password.cy.ts +71 -0
  28. package/tests/cypress/e2e/_selectors/settings-profile.cy.ts +82 -0
  29. package/tests/cypress/e2e/_selectors/settings-teams.cy.ts +68 -0
  30. package/tests/cypress/e2e/_selectors/superadmin.cy.ts +185 -0
  31. package/tests/cypress/e2e/_selectors/tasks.cy.ts +242 -0
  32. package/tests/cypress/e2e/_selectors/taxonomies.cy.ts +126 -0
  33. package/tests/cypress/e2e/_selectors/teams.cy.ts +142 -0
  34. package/tests/cypress/e2e/_superadmin/all-teams.bdd.md +261 -0
  35. package/tests/cypress/e2e/_superadmin/all-teams.cy.ts +177 -0
  36. package/tests/cypress/e2e/_superadmin/all-users.bdd.md +406 -0
  37. package/tests/cypress/e2e/_superadmin/all-users.cy.ts +294 -0
  38. package/tests/cypress/e2e/_superadmin/dashboard.bdd.md +235 -0
  39. package/tests/cypress/e2e/_superadmin/dashboard.cy.ts +149 -0
  40. package/tests/cypress/e2e/_superadmin/subscriptions-overview.bdd.md +290 -0
  41. package/tests/cypress/e2e/_superadmin/subscriptions-overview.cy.ts +194 -0
  42. package/tests/cypress/e2e/ai/ai-usage.cy.ts +209 -0
  43. package/tests/cypress/e2e/ai/chat-api.cy.ts +107 -0
  44. package/tests/cypress/e2e/ai/guardrails.cy.ts +332 -0
  45. package/tests/cypress/e2e/api/billing/BillingAPIController.js +319 -0
  46. package/tests/cypress/e2e/api/billing/check-action.cy.ts +326 -0
  47. package/tests/cypress/e2e/api/billing/checkout.cy.ts +358 -0
  48. package/tests/cypress/e2e/api/billing/lifecycle.cy.ts +423 -0
  49. package/tests/cypress/e2e/api/billing/plans/README.md +345 -0
  50. package/tests/cypress/e2e/api/billing/plans/business.cy.ts +412 -0
  51. package/tests/cypress/e2e/api/billing/plans/downgrade.cy.ts +510 -0
  52. package/tests/cypress/e2e/api/billing/plans/fixtures/billing-plans.json +163 -0
  53. package/tests/cypress/e2e/api/billing/plans/free.cy.ts +500 -0
  54. package/tests/cypress/e2e/api/billing/plans/pro.cy.ts +497 -0
  55. package/tests/cypress/e2e/api/billing/plans/starter.cy.ts +342 -0
  56. package/tests/cypress/e2e/api/billing/portal.cy.ts +313 -0
  57. package/tests/cypress/e2e/api/devtools/registries.bdd.md +300 -0
  58. package/tests/cypress/e2e/api/devtools/registries.cy.ts +368 -0
  59. package/tests/cypress/e2e/api/entities/blocks-scope.cy.ts +396 -0
  60. package/tests/cypress/e2e/api/entities/customers-crud.cy.ts +648 -0
  61. package/tests/cypress/e2e/api/entities/customers-metas.cy.ts +839 -0
  62. package/tests/cypress/e2e/api/entities/pages-crud.cy.ts +425 -0
  63. package/tests/cypress/e2e/api/entities/pages-status.cy.ts +335 -0
  64. package/tests/cypress/e2e/api/entities/post-categories-crud.cy.ts +610 -0
  65. package/tests/cypress/e2e/api/entities/posts-crud.cy.ts +709 -0
  66. package/tests/cypress/e2e/api/entities/posts-status.cy.ts +396 -0
  67. package/tests/cypress/e2e/api/entities/tasks-crud.cy.ts +602 -0
  68. package/tests/cypress/e2e/api/entities/tasks-metas.cy.ts +878 -0
  69. package/tests/cypress/e2e/api/entities/users-crud.cy.ts +469 -0
  70. package/tests/cypress/e2e/api/entities/users-metas.cy.ts +913 -0
  71. package/tests/cypress/e2e/api/entities/users-security.cy.ts +375 -0
  72. package/tests/cypress/e2e/api/scheduled-actions/cron-endpoint.bdd.md +375 -0
  73. package/tests/cypress/e2e/api/scheduled-actions/cron-endpoint.cy.ts +346 -0
  74. package/tests/cypress/e2e/api/scheduled-actions/devtools-endpoint.bdd.md +451 -0
  75. package/tests/cypress/e2e/api/scheduled-actions/devtools-endpoint.cy.ts +447 -0
  76. package/tests/cypress/e2e/api/scheduled-actions/scheduling.bdd.md +649 -0
  77. package/tests/cypress/e2e/api/scheduled-actions/scheduling.cy.ts +333 -0
  78. package/tests/cypress/e2e/api/settings/api-keys.crud.cy.ts +923 -0
  79. package/tests/cypress/e2e/uat/auth/app-roles/developer-login.bdd.md +231 -0
  80. package/tests/cypress/e2e/uat/auth/app-roles/developer-login.cy.ts +144 -0
  81. package/tests/cypress/e2e/uat/auth/app-roles/superadmin-login.bdd.md +118 -0
  82. package/tests/cypress/e2e/uat/auth/app-roles/superadmin-login.cy.ts +84 -0
  83. package/tests/cypress/e2e/uat/auth/custom-roles/editor-login.bdd.md +288 -0
  84. package/tests/cypress/e2e/uat/auth/custom-roles/editor-login.cy.ts +188 -0
  85. package/tests/cypress/e2e/uat/auth/login-logout.bdd.md +160 -0
  86. package/tests/cypress/e2e/uat/auth/login-logout.cy.ts +116 -0
  87. package/tests/cypress/e2e/uat/auth/password-reset.bdd.md +289 -0
  88. package/tests/cypress/e2e/uat/auth/password-reset.cy.ts +200 -0
  89. package/tests/cypress/e2e/uat/auth/team-roles/admin-login.bdd.md +225 -0
  90. package/tests/cypress/e2e/uat/auth/team-roles/admin-login.cy.ts +148 -0
  91. package/tests/cypress/e2e/uat/auth/team-roles/member-login.bdd.md +251 -0
  92. package/tests/cypress/e2e/uat/auth/team-roles/member-login.cy.ts +163 -0
  93. package/tests/cypress/e2e/uat/auth/team-roles/owner-login.bdd.md +231 -0
  94. package/tests/cypress/e2e/uat/auth/team-roles/owner-login.cy.ts +141 -0
  95. package/tests/cypress/e2e/uat/billing/extended.bdd.md +273 -0
  96. package/tests/cypress/e2e/uat/billing/extended.cy.ts +209 -0
  97. package/tests/cypress/e2e/uat/billing/feature-gates.bdd.md +407 -0
  98. package/tests/cypress/e2e/uat/billing/feature-gates.cy.ts +307 -0
  99. package/tests/cypress/e2e/uat/billing/page.bdd.md +329 -0
  100. package/tests/cypress/e2e/uat/billing/page.cy.ts +250 -0
  101. package/tests/cypress/e2e/uat/billing/status.bdd.md +190 -0
  102. package/tests/cypress/e2e/uat/billing/status.cy.ts +145 -0
  103. package/tests/cypress/e2e/uat/billing/team-switch.bdd.md +156 -0
  104. package/tests/cypress/e2e/uat/billing/team-switch.cy.ts +122 -0
  105. package/tests/cypress/e2e/uat/billing/usage.bdd.md +218 -0
  106. package/tests/cypress/e2e/uat/billing/usage.cy.ts +176 -0
  107. package/tests/cypress/e2e/uat/blocks/hero.bdd.md +124 -0
  108. package/tests/cypress/e2e/uat/blocks/hero.cy.ts +56 -0
  109. package/tests/cypress/e2e/uat/devtools/api-tester.cy.ts +390 -0
  110. package/tests/cypress/e2e/uat/entities/customers/member.bdd.md +275 -0
  111. package/tests/cypress/e2e/uat/entities/customers/member.cy.ts +122 -0
  112. package/tests/cypress/e2e/uat/entities/customers/owner.bdd.md +243 -0
  113. package/tests/cypress/e2e/uat/entities/customers/owner.cy.ts +165 -0
  114. package/tests/cypress/e2e/uat/entities/pages/block-crud.bdd.md +476 -0
  115. package/tests/cypress/e2e/uat/entities/pages/block-crud.cy.ts +486 -0
  116. package/tests/cypress/e2e/uat/entities/pages/block-editor.bdd.md +460 -0
  117. package/tests/cypress/e2e/uat/entities/pages/block-editor.cy.ts +301 -0
  118. package/tests/cypress/e2e/uat/entities/pages/list.bdd.md +432 -0
  119. package/tests/cypress/e2e/uat/entities/pages/list.cy.ts +273 -0
  120. package/tests/cypress/e2e/uat/entities/pages/public-rendering.bdd.md +696 -0
  121. package/tests/cypress/e2e/uat/entities/pages/public-rendering.cy.ts +340 -0
  122. package/tests/cypress/e2e/uat/entities/posts/categories-api-aware.bdd.md +161 -0
  123. package/tests/cypress/e2e/uat/entities/posts/categories-api-aware.cy.ts +104 -0
  124. package/tests/cypress/e2e/uat/entities/posts/categories.bdd.md +375 -0
  125. package/tests/cypress/e2e/uat/entities/posts/categories.cy.ts +241 -0
  126. package/tests/cypress/e2e/uat/entities/posts/editor.bdd.md +429 -0
  127. package/tests/cypress/e2e/uat/entities/posts/editor.cy.ts +257 -0
  128. package/tests/cypress/e2e/uat/entities/posts/list.bdd.md +340 -0
  129. package/tests/cypress/e2e/uat/entities/posts/list.cy.ts +177 -0
  130. package/tests/cypress/e2e/uat/entities/posts/public.bdd.md +614 -0
  131. package/tests/cypress/e2e/uat/entities/posts/public.cy.ts +249 -0
  132. package/tests/cypress/e2e/uat/entities/tasks/member.bdd.md +222 -0
  133. package/tests/cypress/e2e/uat/entities/tasks/member.cy.ts +165 -0
  134. package/tests/cypress/e2e/uat/entities/tasks/owner.bdd.md +419 -0
  135. package/tests/cypress/e2e/uat/entities/tasks/owner.cy.ts +191 -0
  136. package/tests/cypress/e2e/uat/roles/editor-role.bdd.md +552 -0
  137. package/tests/cypress/e2e/uat/roles/editor-role.cy.ts +210 -0
  138. package/tests/cypress/e2e/uat/roles/member-restrictions.bdd.md +450 -0
  139. package/tests/cypress/e2e/uat/roles/member-restrictions.cy.ts +189 -0
  140. package/tests/cypress/e2e/uat/roles/owner-full-crud.bdd.md +530 -0
  141. package/tests/cypress/e2e/uat/roles/owner-full-crud.cy.ts +247 -0
  142. package/tests/cypress/e2e/uat/scheduled-actions/devtools-ui.bdd.md +736 -0
  143. package/tests/cypress/e2e/uat/scheduled-actions/devtools-ui.cy.ts +740 -0
  144. package/tests/cypress/e2e/uat/teams/roles-matrix.bdd.md +553 -0
  145. package/tests/cypress/e2e/uat/teams/roles-matrix.cy.ts +185 -0
  146. package/tests/cypress/e2e/uat/teams/switcher.bdd.md +1151 -0
  147. package/tests/cypress/e2e/uat/teams/switcher.cy.ts +497 -0
  148. package/tests/cypress/e2e/uat/teams/team-switcher.md +198 -0
  149. package/tests/cypress/fixtures/blocks.json +218 -0
  150. package/tests/cypress/fixtures/entities.json +78 -0
  151. package/tests/cypress/fixtures/page-builder.json +21 -0
  152. package/tests/cypress/src/components/CategoriesPOM.ts +382 -0
  153. package/tests/cypress/src/components/CustomersPOM.ts +439 -0
  154. package/tests/cypress/src/components/DevKeyringPOM.ts +160 -0
  155. package/tests/cypress/src/components/EntityForm.ts +375 -0
  156. package/tests/cypress/src/components/EntityList.ts +389 -0
  157. package/tests/cypress/src/components/PageBuilderPOM.ts +710 -0
  158. package/tests/cypress/src/components/PostEditorPOM.ts +370 -0
  159. package/tests/cypress/src/components/PostsListPOM.ts +223 -0
  160. package/tests/cypress/src/components/PublicPagePOM.ts +447 -0
  161. package/tests/cypress/src/components/PublicPostPOM.ts +146 -0
  162. package/tests/cypress/src/components/TasksPOM.ts +272 -0
  163. package/tests/cypress/src/components/TeamSwitcherPOM.ts +450 -0
  164. package/tests/cypress/src/components/index.ts +21 -0
  165. package/tests/cypress/src/controllers/ApiKeysAPIController.js +178 -0
  166. package/tests/cypress/src/controllers/BaseAPIController.js +317 -0
  167. package/tests/cypress/src/controllers/CustomerAPIController.js +251 -0
  168. package/tests/cypress/src/controllers/PagesAPIController.js +226 -0
  169. package/tests/cypress/src/controllers/PostsAPIController.js +250 -0
  170. package/tests/cypress/src/controllers/TaskAPIController.js +240 -0
  171. package/tests/cypress/src/controllers/UsersAPIController.js +242 -0
  172. package/tests/cypress/src/controllers/index.js +25 -0
  173. package/tests/cypress/src/core/AuthPOM.ts +450 -0
  174. package/tests/cypress/src/core/BasePOM.ts +86 -0
  175. package/tests/cypress/src/core/BlockEditorBasePOM.ts +576 -0
  176. package/tests/cypress/src/core/DashboardEntityPOM.ts +692 -0
  177. package/tests/cypress/src/core/index.ts +14 -0
  178. package/tests/cypress/src/entities/CustomersPOM.ts +172 -0
  179. package/tests/cypress/src/entities/PagesPOM.ts +137 -0
  180. package/tests/cypress/src/entities/PostsPOM.ts +137 -0
  181. package/tests/cypress/src/entities/TasksPOM.ts +176 -0
  182. package/tests/cypress/src/entities/index.ts +14 -0
  183. package/tests/cypress/src/features/BillingPOM.ts +385 -0
  184. package/tests/cypress/src/features/DashboardPOM.ts +245 -0
  185. package/tests/cypress/src/features/DevtoolsPOM.ts +739 -0
  186. package/tests/cypress/src/features/PageBuilderPOM.ts +263 -0
  187. package/tests/cypress/src/features/PostEditorPOM.ts +313 -0
  188. package/tests/cypress/src/features/ScheduledActionsPOM.ts +463 -0
  189. package/tests/cypress/src/features/SettingsPOM.ts +362 -0
  190. package/tests/cypress/src/features/SuperadminPOM.ts +331 -0
  191. package/tests/cypress/src/features/SuperadminTeamRolesPOM.ts +285 -0
  192. package/tests/cypress/src/features/index.ts +28 -0
  193. package/tests/cypress/src/helpers/ApiInterceptor.ts +177 -0
  194. package/tests/cypress/src/index.ts +101 -0
  195. package/tests/cypress/src/pages/dashboard/Dashboard.js +677 -0
  196. package/tests/cypress/src/pages/dashboard/DashboardPage.js +43 -0
  197. package/tests/cypress/src/pages/dashboard/DashboardStats.js +546 -0
  198. package/tests/cypress/src/pages/dashboard/index.js +6 -0
  199. package/tests/cypress/src/pages/index.js +5 -0
  200. package/tests/cypress/src/pages/public/FeaturesPage.js +28 -0
  201. package/tests/cypress/src/pages/public/LandingPage.js +69 -0
  202. package/tests/cypress/src/pages/public/PricingPage.js +33 -0
  203. package/tests/cypress/src/pages/public/index.js +6 -0
  204. package/tests/cypress/src/selectors.ts +46 -0
  205. package/tests/cypress/src/session-helpers.ts +500 -0
  206. package/tests/cypress/support/doc-commands.ts +260 -0
  207. package/tests/cypress/support/e2e.ts +89 -0
  208. package/tests/cypress.config.ts +165 -0
  209. package/tests/jest/components/post-header.test.tsx +377 -0
  210. package/tests/jest/config/role-config.test.ts +529 -0
  211. package/tests/jest/jest.config.ts +81 -0
  212. package/tests/jest/langchain/COVERAGE.md +372 -0
  213. package/tests/jest/langchain/guardrails.test.ts +465 -0
  214. package/tests/jest/langchain/streaming.test.ts +367 -0
  215. package/tests/jest/langchain/token-tracker.test.ts +455 -0
  216. package/tests/jest/langchain/tracer-callbacks.test.ts +881 -0
  217. package/tests/jest/langchain/tracer.test.ts +823 -0
  218. package/tests/jest/user-roles/role-helpers.test.ts +432 -0
  219. package/tests/jest/validation/categories.test.ts +429 -0
  220. package/tests/jest/validation/posts.test.ts +546 -0
  221. package/tests/tsconfig.json +15 -0
@@ -0,0 +1,839 @@
1
+ /// <reference types="cypress" />
2
+
3
+ /**
4
+ * Customers API - Metadata Tests
5
+ *
6
+ * Comprehensive test suite for Customer API endpoints with metadata functionality.
7
+ * Tests GET, POST, PATCH, DELETE operations with various metadata scenarios.
8
+ * Tests metadata parameter handling, merge behavior, and upsert functionality.
9
+ */
10
+
11
+ import * as allure from 'allure-cypress'
12
+
13
+ const CustomerAPIController = require('../../../src/controllers/CustomerAPIController.js')
14
+
15
+ describe('Customers API - Metadata Operations', {
16
+ tags: ['@api', '@feat-customers', '@metas', '@regression']
17
+ }, () => {
18
+ // Test constants
19
+ const SUPERADMIN_API_KEY = 'test_api_key_for_testing_purposes_only_not_a_real_secret_key_abc123'
20
+ const TEAM_ID = 'team-tmt-001'
21
+ const BASE_URL = Cypress.config('baseUrl') || 'http://localhost:5173'
22
+
23
+ // Controller instance
24
+ let customerAPI: InstanceType<typeof CustomerAPIController>
25
+
26
+ // Track created customers for cleanup
27
+ let createdCustomers: any[] = []
28
+
29
+ before(() => {
30
+ // Initialize controller with superadmin credentials
31
+ customerAPI = new CustomerAPIController(BASE_URL, SUPERADMIN_API_KEY, TEAM_ID)
32
+ })
33
+
34
+ beforeEach(() => {
35
+ allure.epic('API')
36
+ allure.feature('Customers')
37
+ allure.story('Metadata Operations')
38
+ })
39
+
40
+ afterEach(() => {
41
+ // Cleanup created customers after each test
42
+ createdCustomers.forEach((customer) => {
43
+ if (customer?.id) {
44
+ customerAPI.deleteCustomer(customer.id)
45
+ }
46
+ })
47
+ createdCustomers = []
48
+ })
49
+
50
+ // ============================================
51
+ // GET /api/v1/customers - List with Metadata
52
+ // ============================================
53
+ describe('GET /api/v1/customers - List with Metadata', () => {
54
+ let testCustomer: any
55
+
56
+ beforeEach(() => {
57
+ // Create a customer with metadata for testing
58
+ const customerData = customerAPI.generateRandomCustomerData()
59
+ customerData.metas = {
60
+ contactPreferences: {
61
+ preferredTime: 'morning',
62
+ preferredChannel: 'phone'
63
+ },
64
+ billing: {
65
+ paymentTerms: 'net30',
66
+ creditLimit: 50000
67
+ }
68
+ }
69
+
70
+ return customerAPI.createCustomer(customerData).then((response: any) => {
71
+ expect(response.status).to.eq(201)
72
+ testCustomer = response.body.data
73
+ createdCustomers.push(testCustomer)
74
+ })
75
+ })
76
+
77
+ it('CUST_META_001: Should list without metas property when no metas param', () => {
78
+ customerAPI.getCustomers().then((response: any) => {
79
+ customerAPI.validateSuccessResponse(response, 200)
80
+
81
+ // When no metas param, response should not include metas property
82
+ response.body.data.forEach((customer: any) => {
83
+ expect(customer).to.not.have.property('metas')
84
+ })
85
+
86
+ cy.log('List without metas param - no metas property in response')
87
+ })
88
+ })
89
+
90
+ it('CUST_META_002: Should list with metas=all includes all metadata groups', () => {
91
+ customerAPI.getCustomers({ metas: 'all' }).then((response: any) => {
92
+ customerAPI.validateSuccessResponse(response, 200)
93
+
94
+ // Find our test customer
95
+ const foundCustomer = response.body.data.find((c: any) => c.id === testCustomer.id)
96
+ if (foundCustomer) {
97
+ expect(foundCustomer).to.have.property('metas')
98
+ expect(foundCustomer.metas).to.be.an('object')
99
+ expect(foundCustomer.metas).to.have.property('contactPreferences')
100
+ expect(foundCustomer.metas).to.have.property('billing')
101
+ }
102
+
103
+ cy.log('List with metas=all includes all metadata groups')
104
+ })
105
+ })
106
+
107
+ it('CUST_META_003: Should list with metas=key1 includes only specified metaKey', () => {
108
+ customerAPI.getCustomers({ metas: 'contactPreferences' }).then((response: any) => {
109
+ customerAPI.validateSuccessResponse(response, 200)
110
+
111
+ // Find our test customer
112
+ const foundCustomer = response.body.data.find((c: any) => c.id === testCustomer.id)
113
+ if (foundCustomer) {
114
+ expect(foundCustomer).to.have.property('metas')
115
+ expect(foundCustomer.metas).to.have.property('contactPreferences')
116
+ // Should NOT have billing when only requesting contactPreferences
117
+ expect(foundCustomer.metas).to.not.have.property('billing')
118
+ }
119
+
120
+ cy.log('List with metas=contactPreferences includes only that key')
121
+ })
122
+ })
123
+
124
+ it('CUST_META_004: Should list with metas=key1,key2 includes multiple metaKeys', () => {
125
+ customerAPI.getCustomers({ metas: 'contactPreferences,billing' }).then((response: any) => {
126
+ customerAPI.validateSuccessResponse(response, 200)
127
+
128
+ // Find our test customer
129
+ const foundCustomer = response.body.data.find((c: any) => c.id === testCustomer.id)
130
+ if (foundCustomer) {
131
+ expect(foundCustomer).to.have.property('metas')
132
+ expect(foundCustomer.metas).to.have.property('contactPreferences')
133
+ expect(foundCustomer.metas).to.have.property('billing')
134
+ }
135
+
136
+ cy.log('List with metas=contactPreferences,billing includes both keys')
137
+ })
138
+ })
139
+
140
+ it('CUST_META_005: Should list with non-existent metaKey returns metas without key', () => {
141
+ customerAPI.getCustomers({ metas: 'nonExistentKey' }).then((response: any) => {
142
+ customerAPI.validateSuccessResponse(response, 200)
143
+
144
+ // Find our test customer - should have empty metas or metas without the key
145
+ const foundCustomer = response.body.data.find((c: any) => c.id === testCustomer.id)
146
+ if (foundCustomer) {
147
+ expect(foundCustomer).to.have.property('metas')
148
+ expect(foundCustomer.metas).to.not.have.property('nonExistentKey')
149
+ }
150
+
151
+ cy.log('List with non-existent metaKey returns metas without that key')
152
+ })
153
+ })
154
+
155
+ it('CUST_META_006: Should list with pagination + metas works together', () => {
156
+ customerAPI.getCustomers({ page: 1, limit: 5, metas: 'all' }).then((response: any) => {
157
+ customerAPI.validatePaginatedResponse(response)
158
+ expect(response.body.info.page).to.eq(1)
159
+ expect(response.body.info.limit).to.eq(5)
160
+
161
+ // Customers should have metas
162
+ response.body.data.forEach((customer: any) => {
163
+ expect(customer).to.have.property('metas')
164
+ })
165
+
166
+ cy.log('Pagination with metas works correctly')
167
+ })
168
+ })
169
+ })
170
+
171
+ // ============================================
172
+ // GET /api/v1/customers/{id} - Get Single with Metadata
173
+ // ============================================
174
+ describe('GET /api/v1/customers/{id} - Get Single with Metadata', () => {
175
+ let testCustomer: any
176
+
177
+ beforeEach(() => {
178
+ // Create a customer with metadata for testing
179
+ const customerData = customerAPI.generateRandomCustomerData()
180
+ customerData.metas = {
181
+ contactPreferences: {
182
+ preferredTime: 'afternoon',
183
+ preferredChannel: 'email'
184
+ },
185
+ billing: {
186
+ paymentTerms: 'net60',
187
+ creditLimit: 100000
188
+ }
189
+ }
190
+
191
+ return customerAPI.createCustomer(customerData).then((response: any) => {
192
+ expect(response.status).to.eq(201)
193
+ testCustomer = response.body.data
194
+ createdCustomers.push(testCustomer)
195
+ })
196
+ })
197
+
198
+ it('CUST_META_010: Should get without metas param - no metas property', () => {
199
+ customerAPI.getCustomerById(testCustomer.id).then((response: any) => {
200
+ customerAPI.validateSuccessResponse(response, 200)
201
+ expect(response.body.data).to.not.have.property('metas')
202
+
203
+ cy.log('Get without metas param - no metas in response')
204
+ })
205
+ })
206
+
207
+ it('CUST_META_011: Should get with metas=all includes all metadata groups', () => {
208
+ customerAPI.getCustomerById(testCustomer.id, { metas: 'all' }).then((response: any) => {
209
+ customerAPI.validateSuccessResponse(response, 200)
210
+ expect(response.body.data).to.have.property('metas')
211
+ expect(response.body.data.metas).to.have.property('contactPreferences')
212
+ expect(response.body.data.metas).to.have.property('billing')
213
+
214
+ cy.log('Get with metas=all includes all metadata')
215
+ })
216
+ })
217
+
218
+ it('CUST_META_012: Should get with metas=key1 includes only specified metaKey', () => {
219
+ customerAPI.getCustomerById(testCustomer.id, { metas: 'contactPreferences' }).then((response: any) => {
220
+ customerAPI.validateSuccessResponse(response, 200)
221
+ expect(response.body.data).to.have.property('metas')
222
+ expect(response.body.data.metas).to.have.property('contactPreferences')
223
+ expect(response.body.data.metas).to.not.have.property('billing')
224
+
225
+ cy.log('Get with metas=contactPreferences includes only that key')
226
+ })
227
+ })
228
+
229
+ it('CUST_META_013: Should get with metas=key1,key2 includes multiple metaKeys', () => {
230
+ customerAPI
231
+ .getCustomerById(testCustomer.id, { metas: 'contactPreferences,billing' })
232
+ .then((response: any) => {
233
+ customerAPI.validateSuccessResponse(response, 200)
234
+ expect(response.body.data).to.have.property('metas')
235
+ expect(response.body.data.metas).to.have.property('contactPreferences')
236
+ expect(response.body.data.metas).to.have.property('billing')
237
+
238
+ cy.log('Get with multiple metaKeys includes both')
239
+ })
240
+ })
241
+
242
+ it('CUST_META_014: Should get customer without metadata returns metas: {}', () => {
243
+ // Create a customer without metadata
244
+ const customerData = customerAPI.generateRandomCustomerData()
245
+
246
+ customerAPI.createCustomer(customerData).then((createResponse: any) => {
247
+ expect(createResponse.status).to.eq(201)
248
+ createdCustomers.push(createResponse.body.data)
249
+
250
+ customerAPI
251
+ .getCustomerById(createResponse.body.data.id, { metas: 'all' })
252
+ .then((response: any) => {
253
+ customerAPI.validateSuccessResponse(response, 200)
254
+ expect(response.body.data).to.have.property('metas')
255
+ expect(response.body.data.metas).to.deep.eq({})
256
+
257
+ cy.log('Customer without metadata returns metas: {}')
258
+ })
259
+ })
260
+ })
261
+
262
+ it('CUST_META_015: Should get with non-existent key returns empty metas', () => {
263
+ customerAPI
264
+ .getCustomerById(testCustomer.id, { metas: 'nonExistentKey' })
265
+ .then((response: any) => {
266
+ customerAPI.validateSuccessResponse(response, 200)
267
+ expect(response.body.data).to.have.property('metas')
268
+ expect(response.body.data.metas).to.not.have.property('nonExistentKey')
269
+
270
+ cy.log('Get with non-existent metaKey returns empty metas')
271
+ })
272
+ })
273
+ })
274
+
275
+ // ============================================
276
+ // POST /api/v1/customers - Create with Metadata
277
+ // ============================================
278
+ describe('POST /api/v1/customers - Create with Metadata', () => {
279
+ it('CUST_META_020: Should create without metas - no metas in response', () => {
280
+ const customerData = customerAPI.generateRandomCustomerData()
281
+
282
+ customerAPI.createCustomer(customerData).then((response: any) => {
283
+ customerAPI.validateSuccessResponse(response, 201)
284
+ createdCustomers.push(response.body.data)
285
+
286
+ // Response should not have metas when not provided
287
+ expect(response.body.data).to.not.have.property('metas')
288
+
289
+ cy.log('Created customer without metas')
290
+ })
291
+ })
292
+
293
+ it('CUST_META_021: Should create with one meta group - response includes metas', () => {
294
+ const customerData = customerAPI.generateRandomCustomerData()
295
+ customerData.metas = customerAPI.generateSampleMetadata('contactPreferences')
296
+
297
+ customerAPI.createCustomer(customerData).then((response: any) => {
298
+ customerAPI.validateSuccessResponse(response, 201)
299
+ createdCustomers.push(response.body.data)
300
+
301
+ expect(response.body.data).to.have.property('metas')
302
+ expect(response.body.data.metas).to.have.property('contactPreferences')
303
+
304
+ cy.log('Created customer with one meta group')
305
+ })
306
+ })
307
+
308
+ it('CUST_META_022: Should create with multiple groups - all groups created', () => {
309
+ const customerData = customerAPI.generateRandomCustomerData()
310
+ customerData.metas = {
311
+ ...customerAPI.generateSampleMetadata('contactPreferences'),
312
+ ...customerAPI.generateSampleMetadata('billing'),
313
+ ...customerAPI.generateSampleMetadata('notes')
314
+ }
315
+
316
+ customerAPI.createCustomer(customerData).then((response: any) => {
317
+ customerAPI.validateSuccessResponse(response, 201)
318
+ createdCustomers.push(response.body.data)
319
+
320
+ expect(response.body.data).to.have.property('metas')
321
+ expect(response.body.data.metas).to.have.property('contactPreferences')
322
+ expect(response.body.data.metas).to.have.property('billing')
323
+ expect(response.body.data.metas).to.have.property('notes')
324
+
325
+ cy.log('Created customer with multiple meta groups')
326
+ })
327
+ })
328
+
329
+ it('CUST_META_023: Should create with nested structure - structure preserved', () => {
330
+ const customerData = customerAPI.generateRandomCustomerData()
331
+ customerData.metas = {
332
+ deepNested: {
333
+ level1: {
334
+ level2: {
335
+ level3: {
336
+ value: 'deeply nested value',
337
+ array: [1, 2, 3]
338
+ }
339
+ }
340
+ }
341
+ }
342
+ }
343
+
344
+ customerAPI.createCustomer(customerData).then((response: any) => {
345
+ customerAPI.validateSuccessResponse(response, 201)
346
+ createdCustomers.push(response.body.data)
347
+
348
+ expect(response.body.data.metas).to.have.property('deepNested')
349
+ expect(response.body.data.metas.deepNested.level1.level2.level3.value).to.eq(
350
+ 'deeply nested value'
351
+ )
352
+ expect(response.body.data.metas.deepNested.level1.level2.level3.array).to.deep.eq([1, 2, 3])
353
+
354
+ cy.log('Created customer with nested metadata structure')
355
+ })
356
+ })
357
+
358
+ it('CUST_META_024: Should reject create with only metas (no required fields)', () => {
359
+ const invalidData = {
360
+ metas: customerAPI.generateSampleMetadata('contactPreferences')
361
+ }
362
+
363
+ customerAPI.createCustomer(invalidData).then((response: any) => {
364
+ expect(response.status).to.eq(400)
365
+ expect(response.body).to.have.property('success', false)
366
+
367
+ cy.log('Create with only metas rejected (required fields missing)')
368
+ })
369
+ })
370
+
371
+ it('CUST_META_025: Should handle create with invalid metas (string instead of object)', () => {
372
+ const customerData = customerAPI.generateRandomCustomerData()
373
+ customerData.metas = 'invalid string metas'
374
+
375
+ customerAPI.createCustomer(customerData).then((response: any) => {
376
+ // Should either reject or ignore invalid metas
377
+ if (response.status === 201) {
378
+ // If created, metas should be ignored or empty
379
+ createdCustomers.push(response.body.data)
380
+ cy.log('Invalid metas ignored, customer created')
381
+ } else {
382
+ expect(response.body).to.have.property('success', false)
383
+ cy.log('Invalid metas rejected')
384
+ }
385
+ })
386
+ })
387
+ })
388
+
389
+ // ============================================
390
+ // PATCH /api/v1/customers/{id} - Update Metadata
391
+ // ============================================
392
+ describe('PATCH /api/v1/customers/{id} - Update Metadata', () => {
393
+ let testCustomer: any
394
+
395
+ beforeEach(() => {
396
+ // Create a customer with initial metadata
397
+ const customerData = customerAPI.generateRandomCustomerData()
398
+ customerData.metas = {
399
+ contactPreferences: {
400
+ preferredTime: 'morning',
401
+ preferredChannel: 'phone',
402
+ language: 'en'
403
+ }
404
+ }
405
+
406
+ return customerAPI.createCustomer(customerData).then((response: any) => {
407
+ expect(response.status).to.eq(201)
408
+ testCustomer = response.body.data
409
+ createdCustomers.push(testCustomer)
410
+ })
411
+ })
412
+
413
+ it('CUST_META_030: Should update only customer data - metas unchanged, not in response', () => {
414
+ customerAPI
415
+ .updateCustomer(testCustomer.id, { phone: '+1-555-9999' })
416
+ .then((response: any) => {
417
+ customerAPI.validateSuccessResponse(response, 200)
418
+ expect(response.body.data.phone).to.eq('+1-555-9999')
419
+ // Response should not include metas when not updating metas
420
+ expect(response.body.data).to.not.have.property('metas')
421
+
422
+ cy.log('Updated customer data only - metas not in response')
423
+ })
424
+ })
425
+
426
+ it('CUST_META_031: Should update customer data + metas - both updated', () => {
427
+ const updateData = {
428
+ phone: '+1-555-8888',
429
+ metas: {
430
+ contactPreferences: {
431
+ preferredTime: 'evening'
432
+ }
433
+ }
434
+ }
435
+
436
+ customerAPI.updateCustomer(testCustomer.id, updateData).then((response: any) => {
437
+ customerAPI.validateSuccessResponse(response, 200)
438
+ expect(response.body.data.phone).to.eq('+1-555-8888')
439
+ expect(response.body.data).to.have.property('metas')
440
+ expect(response.body.data.metas.contactPreferences.preferredTime).to.eq('evening')
441
+
442
+ cy.log('Updated both customer data and metas')
443
+ })
444
+ })
445
+
446
+ it('CUST_META_032: Should update only metas (requires entity field)', () => {
447
+ // Generic entity API requires at least one entity field
448
+ const updateData = {
449
+ name: testCustomer.name, // Include entity field (no-op)
450
+ metas: {
451
+ contactPreferences: {
452
+ preferredTime: 'night'
453
+ }
454
+ }
455
+ }
456
+
457
+ customerAPI.updateCustomer(testCustomer.id, updateData).then((response: any) => {
458
+ customerAPI.validateSuccessResponse(response, 200)
459
+ expect(response.body.data).to.have.property('metas')
460
+ expect(response.body.data.metas.contactPreferences.preferredTime).to.eq('night')
461
+
462
+ cy.log('Updated only metas (with required entity field)')
463
+ })
464
+ })
465
+
466
+ it('CUST_META_033: Should merge - add key to existing group', () => {
467
+ const updateData = {
468
+ name: testCustomer.name,
469
+ metas: {
470
+ contactPreferences: {
471
+ newSetting: 'new value'
472
+ }
473
+ }
474
+ }
475
+
476
+ customerAPI.updateCustomer(testCustomer.id, updateData).then((response: any) => {
477
+ customerAPI.validateSuccessResponse(response, 200)
478
+
479
+ // Should have both old and new keys
480
+ expect(response.body.data.metas.contactPreferences).to.have.property('preferredTime')
481
+ expect(response.body.data.metas.contactPreferences).to.have.property('preferredChannel')
482
+ expect(response.body.data.metas.contactPreferences).to.have.property('newSetting')
483
+ expect(response.body.data.metas.contactPreferences.newSetting).to.eq('new value')
484
+
485
+ cy.log('Merge: added new key while preserving existing keys')
486
+ })
487
+ })
488
+
489
+ it('CUST_META_034: Should merge - modify existing key', () => {
490
+ const updateData = {
491
+ name: testCustomer.name,
492
+ metas: {
493
+ contactPreferences: {
494
+ preferredTime: 'evening' // Overwrite existing
495
+ }
496
+ }
497
+ }
498
+
499
+ customerAPI.updateCustomer(testCustomer.id, updateData).then((response: any) => {
500
+ customerAPI.validateSuccessResponse(response, 200)
501
+
502
+ // preferredTime should be updated, others preserved
503
+ expect(response.body.data.metas.contactPreferences.preferredTime).to.eq('evening')
504
+ expect(response.body.data.metas.contactPreferences.preferredChannel).to.eq('phone')
505
+ expect(response.body.data.metas.contactPreferences.language).to.eq('en')
506
+
507
+ cy.log('Merge: modified existing key, preserved others')
508
+ })
509
+ })
510
+
511
+ it('CUST_META_035: Should upsert - create new metaKey', () => {
512
+ const updateData = {
513
+ name: testCustomer.name,
514
+ metas: {
515
+ billing: {
516
+ paymentTerms: 'net45',
517
+ creditLimit: 75000
518
+ }
519
+ }
520
+ }
521
+
522
+ customerAPI.updateCustomer(testCustomer.id, updateData).then((response: any) => {
523
+ customerAPI.validateSuccessResponse(response, 200)
524
+
525
+ // Should have both contactPreferences and new billing
526
+ expect(response.body.data.metas).to.have.property('contactPreferences')
527
+ expect(response.body.data.metas).to.have.property('billing')
528
+ expect(response.body.data.metas.billing.paymentTerms).to.eq('net45')
529
+
530
+ cy.log('Upsert: created new metaKey')
531
+ })
532
+ })
533
+
534
+ it('CUST_META_036: Should update multiple groups', () => {
535
+ const updateData = {
536
+ name: testCustomer.name,
537
+ metas: {
538
+ contactPreferences: {
539
+ preferredTime: 'afternoon'
540
+ },
541
+ billing: {
542
+ paymentTerms: 'net30'
543
+ },
544
+ notes: {
545
+ priority: 'high'
546
+ }
547
+ }
548
+ }
549
+
550
+ customerAPI.updateCustomer(testCustomer.id, updateData).then((response: any) => {
551
+ customerAPI.validateSuccessResponse(response, 200)
552
+
553
+ expect(response.body.data.metas).to.have.property('contactPreferences')
554
+ expect(response.body.data.metas).to.have.property('billing')
555
+ expect(response.body.data.metas).to.have.property('notes')
556
+
557
+ cy.log('Updated multiple meta groups')
558
+ })
559
+ })
560
+
561
+ it('CUST_META_037: Should update nested objects', () => {
562
+ // First create customer with nested metadata
563
+ const customerData = customerAPI.generateRandomCustomerData()
564
+ customerData.metas = {
565
+ settings: {
566
+ display: {
567
+ theme: 'dark',
568
+ fontSize: 14
569
+ }
570
+ }
571
+ }
572
+
573
+ customerAPI.createCustomer(customerData).then((createResponse: any) => {
574
+ expect(createResponse.status).to.eq(201)
575
+ createdCustomers.push(createResponse.body.data)
576
+
577
+ // Update nested value - include all values we want to keep
578
+ // Deep merge may replace entire nested objects at metaKey level
579
+ const updateData = {
580
+ name: createResponse.body.data.name,
581
+ metas: {
582
+ settings: {
583
+ display: {
584
+ theme: 'light',
585
+ fontSize: 14 // Include to preserve
586
+ }
587
+ }
588
+ }
589
+ }
590
+
591
+ customerAPI
592
+ .updateCustomer(createResponse.body.data.id, updateData)
593
+ .then((response: any) => {
594
+ customerAPI.validateSuccessResponse(response, 200)
595
+ expect(response.body.data.metas.settings.display.theme).to.eq('light')
596
+ expect(response.body.data.metas.settings.display.fontSize).to.eq(14)
597
+
598
+ cy.log('Updated nested object values')
599
+ })
600
+ })
601
+ })
602
+
603
+ it('CUST_META_038: Should reject update with empty metas {} (if no customer fields)', () => {
604
+ const updateData = {
605
+ metas: {}
606
+ }
607
+
608
+ customerAPI.updateCustomer(testCustomer.id, updateData).then((response: any) => {
609
+ expect(response.status).to.eq(400)
610
+ expect(response.body).to.have.property('success', false)
611
+
612
+ cy.log('Update with empty metas and no fields rejected')
613
+ })
614
+ })
615
+
616
+ it('CUST_META_039: Should reject update with invalid metas (string)', () => {
617
+ const updateData = {
618
+ metas: 'invalid string'
619
+ }
620
+
621
+ customerAPI.updateCustomer(testCustomer.id, updateData).then((response: any) => {
622
+ expect(response.status).to.eq(400)
623
+ expect(response.body).to.have.property('success', false)
624
+
625
+ cy.log('Update with invalid metas rejected')
626
+ })
627
+ })
628
+ })
629
+
630
+ // ============================================
631
+ // DELETE /api/v1/customers/{id} - Delete with Metadata
632
+ // ============================================
633
+ describe('DELETE /api/v1/customers/{id} - Delete with Metadata', () => {
634
+ it('CUST_META_050: Should delete customer with metadata', () => {
635
+ // Create customer with metas
636
+ const customerData = customerAPI.generateRandomCustomerData()
637
+ customerData.metas = customerAPI.generateSampleMetadata('contactPreferences')
638
+
639
+ customerAPI.createCustomer(customerData).then((createResponse: any) => {
640
+ expect(createResponse.status).to.eq(201)
641
+ const customerId = createResponse.body.data.id
642
+
643
+ // Delete
644
+ customerAPI.deleteCustomer(customerId).then((response: any) => {
645
+ customerAPI.validateSuccessResponse(response, 200)
646
+ expect(response.body.data).to.have.property('success', true)
647
+
648
+ cy.log('Deleted customer with metadata')
649
+ })
650
+ })
651
+ })
652
+
653
+ it('CUST_META_051: Should verify cascade delete - GET after DELETE returns 404', () => {
654
+ // Create customer with metas
655
+ const customerData = customerAPI.generateRandomCustomerData()
656
+ customerData.metas = {
657
+ contactPreferences: { preferredChannel: 'email' },
658
+ billing: { paymentTerms: 'net30' }
659
+ }
660
+
661
+ customerAPI.createCustomer(customerData).then((createResponse: any) => {
662
+ expect(createResponse.status).to.eq(201)
663
+ const customerId = createResponse.body.data.id
664
+
665
+ // Delete
666
+ customerAPI.deleteCustomer(customerId).then((deleteResponse: any) => {
667
+ expect(deleteResponse.status).to.eq(200)
668
+
669
+ // Verify it's gone
670
+ customerAPI.getCustomerById(customerId, { metas: 'all' }).then((getResponse: any) => {
671
+ expect(getResponse.status).to.eq(404)
672
+
673
+ cy.log('Cascade delete verified - customer and metas gone')
674
+ })
675
+ })
676
+ })
677
+ })
678
+
679
+ it('CUST_META_052: Should delete customer without metadata normally', () => {
680
+ // Create customer without metas
681
+ const customerData = customerAPI.generateRandomCustomerData()
682
+
683
+ customerAPI.createCustomer(customerData).then((createResponse: any) => {
684
+ expect(createResponse.status).to.eq(201)
685
+ const customerId = createResponse.body.data.id
686
+
687
+ // Delete
688
+ customerAPI.deleteCustomer(customerId).then((response: any) => {
689
+ customerAPI.validateSuccessResponse(response, 200)
690
+ expect(response.body.data).to.have.property('success', true)
691
+
692
+ cy.log('Deleted customer without metadata')
693
+ })
694
+ })
695
+ })
696
+ })
697
+
698
+ // ============================================
699
+ // Integration - Lifecycle with Metadata
700
+ // ============================================
701
+ describe('Integration - Lifecycle with Metadata', () => {
702
+ it('CUST_META_100: Should complete full lifecycle with metas', () => {
703
+ // 1. CREATE with metas
704
+ const customerData = customerAPI.generateRandomCustomerData()
705
+ customerData.metas = {
706
+ contactPreferences: {
707
+ preferredTime: 'morning',
708
+ preferredChannel: 'phone'
709
+ }
710
+ }
711
+
712
+ customerAPI.createCustomer(customerData).then((createResponse: any) => {
713
+ customerAPI.validateSuccessResponse(createResponse, 201)
714
+ const customerId = createResponse.body.data.id
715
+ expect(createResponse.body.data.metas).to.have.property('contactPreferences')
716
+
717
+ cy.log('1. Created customer with metas')
718
+
719
+ // 2. READ with metas
720
+ customerAPI.getCustomerById(customerId, { metas: 'all' }).then((readResponse: any) => {
721
+ customerAPI.validateSuccessResponse(readResponse, 200)
722
+ expect(readResponse.body.data.metas).to.have.property('contactPreferences')
723
+
724
+ cy.log('2. Read customer with metas')
725
+
726
+ // 3. UPDATE metas
727
+ const updateData = {
728
+ name: customerData.name,
729
+ metas: {
730
+ contactPreferences: {
731
+ preferredTime: 'evening' // Update
732
+ },
733
+ billing: {
734
+ paymentTerms: 'net30' // New group
735
+ }
736
+ }
737
+ }
738
+
739
+ customerAPI.updateCustomer(customerId, updateData).then((updateResponse: any) => {
740
+ customerAPI.validateSuccessResponse(updateResponse, 200)
741
+ expect(updateResponse.body.data.metas.contactPreferences.preferredTime).to.eq(
742
+ 'evening'
743
+ )
744
+ expect(updateResponse.body.data.metas).to.have.property('billing')
745
+
746
+ cy.log('3. Updated customer metas')
747
+
748
+ // 4. DELETE
749
+ customerAPI.deleteCustomer(customerId).then((deleteResponse: any) => {
750
+ customerAPI.validateSuccessResponse(deleteResponse, 200)
751
+
752
+ cy.log('4. Deleted customer')
753
+ cy.log('Full lifecycle with metas completed!')
754
+ })
755
+ })
756
+ })
757
+ })
758
+ })
759
+
760
+ it('CUST_META_101: Should accumulate metas across multiple updates', () => {
761
+ // Create customer with initial meta
762
+ const customerData = customerAPI.generateRandomCustomerData()
763
+ customerData.metas = {
764
+ contactPreferences: { preferredTime: 'morning' }
765
+ }
766
+
767
+ customerAPI.createCustomer(customerData).then((createResponse: any) => {
768
+ customerAPI.validateSuccessResponse(createResponse, 201)
769
+ const customerId = createResponse.body.data.id
770
+ createdCustomers.push(createResponse.body.data)
771
+
772
+ // Update 1: Add more to contactPreferences
773
+ customerAPI
774
+ .updateCustomer(customerId, {
775
+ name: customerData.name,
776
+ metas: {
777
+ contactPreferences: { preferredChannel: 'email' }
778
+ }
779
+ })
780
+ .then((update1Response: any) => {
781
+ customerAPI.validateSuccessResponse(update1Response, 200)
782
+
783
+ // Should have both preferredTime and preferredChannel
784
+ expect(update1Response.body.data.metas.contactPreferences).to.have.property(
785
+ 'preferredTime'
786
+ )
787
+ expect(update1Response.body.data.metas.contactPreferences).to.have.property(
788
+ 'preferredChannel'
789
+ )
790
+
791
+ // Update 2: Add new billing group
792
+ customerAPI
793
+ .updateCustomer(customerId, {
794
+ name: customerData.name,
795
+ metas: {
796
+ billing: { paymentTerms: 'net30' }
797
+ }
798
+ })
799
+ .then((update2Response: any) => {
800
+ customerAPI.validateSuccessResponse(update2Response, 200)
801
+
802
+ // Should have contactPreferences AND billing
803
+ expect(update2Response.body.data.metas).to.have.property('contactPreferences')
804
+ expect(update2Response.body.data.metas).to.have.property('billing')
805
+
806
+ // Update 3: Add notes group
807
+ customerAPI
808
+ .updateCustomer(customerId, {
809
+ name: customerData.name,
810
+ metas: {
811
+ notes: { priority: 'high' }
812
+ }
813
+ })
814
+ .then((update3Response: any) => {
815
+ customerAPI.validateSuccessResponse(update3Response, 200)
816
+
817
+ // Final state should have all three groups
818
+ expect(update3Response.body.data.metas).to.have.property('contactPreferences')
819
+ expect(update3Response.body.data.metas).to.have.property('billing')
820
+ expect(update3Response.body.data.metas).to.have.property('notes')
821
+
822
+ // Verify all values accumulated
823
+ expect(
824
+ update3Response.body.data.metas.contactPreferences.preferredTime
825
+ ).to.eq('morning')
826
+ expect(
827
+ update3Response.body.data.metas.contactPreferences.preferredChannel
828
+ ).to.eq('email')
829
+ expect(update3Response.body.data.metas.billing.paymentTerms).to.eq('net30')
830
+ expect(update3Response.body.data.metas.notes.priority).to.eq('high')
831
+
832
+ cy.log('Accumulative merge test passed!')
833
+ })
834
+ })
835
+ })
836
+ })
837
+ })
838
+ })
839
+ })