@nextsparkjs/theme-default 0.1.0-beta.2 → 0.1.0-beta.21

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 (222) hide show
  1. package/package.json +8 -4
  2. package/templates/(public)/page.tsx +1 -1
  3. package/tests/cypress/e2e/_devtools/access.bdd.md +262 -0
  4. package/tests/cypress/e2e/_devtools/access.cy.ts +171 -0
  5. package/tests/cypress/e2e/_devtools/navigation.bdd.md +261 -0
  6. package/tests/cypress/e2e/_devtools/navigation.cy.ts +157 -0
  7. package/tests/cypress/e2e/_devtools/pages.bdd.md +303 -0
  8. package/tests/cypress/e2e/_devtools/pages.cy.ts +184 -0
  9. package/tests/cypress/e2e/_docs/README.md +215 -0
  10. package/tests/cypress/e2e/_docs/tutorials/sector7-superadmin-teams.narration.json +155 -0
  11. package/tests/cypress/e2e/_docs/tutorials/sector7-superadmin.cy.ts +390 -0
  12. package/tests/cypress/e2e/_docs/tutorials/teams-system.doc.cy.ts +349 -0
  13. package/tests/cypress/e2e/_docs/tutorials/teams-system.narration.json +165 -0
  14. package/tests/cypress/e2e/_selectors/auth.cy.ts +306 -0
  15. package/tests/cypress/e2e/_selectors/billing.cy.ts +89 -0
  16. package/tests/cypress/e2e/_selectors/dashboard-mobile.cy.ts +113 -0
  17. package/tests/cypress/e2e/_selectors/dashboard-navigation.cy.ts +89 -0
  18. package/tests/cypress/e2e/_selectors/dashboard-sidebar.cy.ts +60 -0
  19. package/tests/cypress/e2e/_selectors/dashboard-topnav.cy.ts +146 -0
  20. package/tests/cypress/e2e/_selectors/devtools.cy.ts +210 -0
  21. package/tests/cypress/e2e/_selectors/global-search.cy.ts +88 -0
  22. package/tests/cypress/e2e/_selectors/pages-editor.cy.ts +179 -0
  23. package/tests/cypress/e2e/_selectors/posts-editor.cy.ts +282 -0
  24. package/tests/cypress/e2e/_selectors/public.cy.ts +112 -0
  25. package/tests/cypress/e2e/_selectors/settings-api-keys.cy.ts +228 -0
  26. package/tests/cypress/e2e/_selectors/settings-billing.cy.ts +105 -0
  27. package/tests/cypress/e2e/_selectors/settings-layout.cy.ts +119 -0
  28. package/tests/cypress/e2e/_selectors/settings-password.cy.ts +71 -0
  29. package/tests/cypress/e2e/_selectors/settings-profile.cy.ts +82 -0
  30. package/tests/cypress/e2e/_selectors/settings-teams.cy.ts +68 -0
  31. package/tests/cypress/e2e/_selectors/superadmin.cy.ts +185 -0
  32. package/tests/cypress/e2e/_selectors/tasks.cy.ts +242 -0
  33. package/tests/cypress/e2e/_selectors/taxonomies.cy.ts +126 -0
  34. package/tests/cypress/e2e/_selectors/teams.cy.ts +142 -0
  35. package/tests/cypress/e2e/_superadmin/all-teams.bdd.md +261 -0
  36. package/tests/cypress/e2e/_superadmin/all-teams.cy.ts +177 -0
  37. package/tests/cypress/e2e/_superadmin/all-users.bdd.md +406 -0
  38. package/tests/cypress/e2e/_superadmin/all-users.cy.ts +294 -0
  39. package/tests/cypress/e2e/_superadmin/dashboard.bdd.md +235 -0
  40. package/tests/cypress/e2e/_superadmin/dashboard.cy.ts +149 -0
  41. package/tests/cypress/e2e/_superadmin/subscriptions-overview.bdd.md +290 -0
  42. package/tests/cypress/e2e/_superadmin/subscriptions-overview.cy.ts +194 -0
  43. package/tests/cypress/e2e/ai/ai-usage.cy.ts +209 -0
  44. package/tests/cypress/e2e/ai/chat-api.cy.ts +107 -0
  45. package/tests/cypress/e2e/ai/guardrails.cy.ts +332 -0
  46. package/tests/cypress/e2e/api/billing/BillingAPIController.js +319 -0
  47. package/tests/cypress/e2e/api/billing/check-action.cy.ts +326 -0
  48. package/tests/cypress/e2e/api/billing/checkout.cy.ts +358 -0
  49. package/tests/cypress/e2e/api/billing/lifecycle.cy.ts +423 -0
  50. package/tests/cypress/e2e/api/billing/plans/README.md +345 -0
  51. package/tests/cypress/e2e/api/billing/plans/business.cy.ts +412 -0
  52. package/tests/cypress/e2e/api/billing/plans/downgrade.cy.ts +510 -0
  53. package/tests/cypress/e2e/api/billing/plans/fixtures/billing-plans.json +163 -0
  54. package/tests/cypress/e2e/api/billing/plans/free.cy.ts +500 -0
  55. package/tests/cypress/e2e/api/billing/plans/pro.cy.ts +497 -0
  56. package/tests/cypress/e2e/api/billing/plans/starter.cy.ts +342 -0
  57. package/tests/cypress/e2e/api/billing/portal.cy.ts +313 -0
  58. package/tests/cypress/e2e/api/devtools/registries.bdd.md +300 -0
  59. package/tests/cypress/e2e/api/devtools/registries.cy.ts +368 -0
  60. package/tests/cypress/e2e/api/entities/blocks-scope.cy.ts +396 -0
  61. package/tests/cypress/e2e/api/entities/customers-crud.cy.ts +648 -0
  62. package/tests/cypress/e2e/api/entities/customers-metas.cy.ts +839 -0
  63. package/tests/cypress/e2e/api/entities/pages-crud.cy.ts +425 -0
  64. package/tests/cypress/e2e/api/entities/pages-status.cy.ts +335 -0
  65. package/tests/cypress/e2e/api/entities/post-categories-crud.cy.ts +610 -0
  66. package/tests/cypress/e2e/api/entities/posts-crud.cy.ts +709 -0
  67. package/tests/cypress/e2e/api/entities/posts-status.cy.ts +396 -0
  68. package/tests/cypress/e2e/api/entities/tasks-crud.cy.ts +602 -0
  69. package/tests/cypress/e2e/api/entities/tasks-metas.cy.ts +878 -0
  70. package/tests/cypress/e2e/api/entities/users-crud.cy.ts +469 -0
  71. package/tests/cypress/e2e/api/entities/users-metas.cy.ts +913 -0
  72. package/tests/cypress/e2e/api/entities/users-security.cy.ts +375 -0
  73. package/tests/cypress/e2e/api/scheduled-actions/cron-endpoint.bdd.md +375 -0
  74. package/tests/cypress/e2e/api/scheduled-actions/cron-endpoint.cy.ts +346 -0
  75. package/tests/cypress/e2e/api/scheduled-actions/devtools-endpoint.bdd.md +451 -0
  76. package/tests/cypress/e2e/api/scheduled-actions/devtools-endpoint.cy.ts +447 -0
  77. package/tests/cypress/e2e/api/scheduled-actions/scheduling.bdd.md +649 -0
  78. package/tests/cypress/e2e/api/scheduled-actions/scheduling.cy.ts +333 -0
  79. package/tests/cypress/e2e/api/settings/api-keys.crud.cy.ts +923 -0
  80. package/tests/cypress/e2e/uat/auth/app-roles/developer-login.bdd.md +231 -0
  81. package/tests/cypress/e2e/uat/auth/app-roles/developer-login.cy.ts +144 -0
  82. package/tests/cypress/e2e/uat/auth/app-roles/superadmin-login.bdd.md +118 -0
  83. package/tests/cypress/e2e/uat/auth/app-roles/superadmin-login.cy.ts +84 -0
  84. package/tests/cypress/e2e/uat/auth/custom-roles/editor-login.bdd.md +288 -0
  85. package/tests/cypress/e2e/uat/auth/custom-roles/editor-login.cy.ts +188 -0
  86. package/tests/cypress/e2e/uat/auth/login-logout.bdd.md +160 -0
  87. package/tests/cypress/e2e/uat/auth/login-logout.cy.ts +116 -0
  88. package/tests/cypress/e2e/uat/auth/password-reset.bdd.md +289 -0
  89. package/tests/cypress/e2e/uat/auth/password-reset.cy.ts +200 -0
  90. package/tests/cypress/e2e/uat/auth/team-roles/admin-login.bdd.md +225 -0
  91. package/tests/cypress/e2e/uat/auth/team-roles/admin-login.cy.ts +148 -0
  92. package/tests/cypress/e2e/uat/auth/team-roles/member-login.bdd.md +251 -0
  93. package/tests/cypress/e2e/uat/auth/team-roles/member-login.cy.ts +163 -0
  94. package/tests/cypress/e2e/uat/auth/team-roles/owner-login.bdd.md +231 -0
  95. package/tests/cypress/e2e/uat/auth/team-roles/owner-login.cy.ts +141 -0
  96. package/tests/cypress/e2e/uat/billing/extended.bdd.md +273 -0
  97. package/tests/cypress/e2e/uat/billing/extended.cy.ts +209 -0
  98. package/tests/cypress/e2e/uat/billing/feature-gates.bdd.md +407 -0
  99. package/tests/cypress/e2e/uat/billing/feature-gates.cy.ts +307 -0
  100. package/tests/cypress/e2e/uat/billing/page.bdd.md +329 -0
  101. package/tests/cypress/e2e/uat/billing/page.cy.ts +250 -0
  102. package/tests/cypress/e2e/uat/billing/status.bdd.md +190 -0
  103. package/tests/cypress/e2e/uat/billing/status.cy.ts +145 -0
  104. package/tests/cypress/e2e/uat/billing/team-switch.bdd.md +156 -0
  105. package/tests/cypress/e2e/uat/billing/team-switch.cy.ts +122 -0
  106. package/tests/cypress/e2e/uat/billing/usage.bdd.md +218 -0
  107. package/tests/cypress/e2e/uat/billing/usage.cy.ts +176 -0
  108. package/tests/cypress/e2e/uat/blocks/hero.bdd.md +124 -0
  109. package/tests/cypress/e2e/uat/blocks/hero.cy.ts +56 -0
  110. package/tests/cypress/e2e/uat/devtools/api-tester.cy.ts +390 -0
  111. package/tests/cypress/e2e/uat/entities/customers/member.bdd.md +275 -0
  112. package/tests/cypress/e2e/uat/entities/customers/member.cy.ts +122 -0
  113. package/tests/cypress/e2e/uat/entities/customers/owner.bdd.md +243 -0
  114. package/tests/cypress/e2e/uat/entities/customers/owner.cy.ts +165 -0
  115. package/tests/cypress/e2e/uat/entities/pages/block-crud.bdd.md +476 -0
  116. package/tests/cypress/e2e/uat/entities/pages/block-crud.cy.ts +486 -0
  117. package/tests/cypress/e2e/uat/entities/pages/block-editor.bdd.md +460 -0
  118. package/tests/cypress/e2e/uat/entities/pages/block-editor.cy.ts +301 -0
  119. package/tests/cypress/e2e/uat/entities/pages/list.bdd.md +432 -0
  120. package/tests/cypress/e2e/uat/entities/pages/list.cy.ts +273 -0
  121. package/tests/cypress/e2e/uat/entities/pages/public-rendering.bdd.md +696 -0
  122. package/tests/cypress/e2e/uat/entities/pages/public-rendering.cy.ts +340 -0
  123. package/tests/cypress/e2e/uat/entities/posts/categories-api-aware.bdd.md +161 -0
  124. package/tests/cypress/e2e/uat/entities/posts/categories-api-aware.cy.ts +104 -0
  125. package/tests/cypress/e2e/uat/entities/posts/categories.bdd.md +375 -0
  126. package/tests/cypress/e2e/uat/entities/posts/categories.cy.ts +241 -0
  127. package/tests/cypress/e2e/uat/entities/posts/editor.bdd.md +429 -0
  128. package/tests/cypress/e2e/uat/entities/posts/editor.cy.ts +257 -0
  129. package/tests/cypress/e2e/uat/entities/posts/list.bdd.md +340 -0
  130. package/tests/cypress/e2e/uat/entities/posts/list.cy.ts +177 -0
  131. package/tests/cypress/e2e/uat/entities/posts/public.bdd.md +614 -0
  132. package/tests/cypress/e2e/uat/entities/posts/public.cy.ts +249 -0
  133. package/tests/cypress/e2e/uat/entities/tasks/member.bdd.md +222 -0
  134. package/tests/cypress/e2e/uat/entities/tasks/member.cy.ts +165 -0
  135. package/tests/cypress/e2e/uat/entities/tasks/owner.bdd.md +419 -0
  136. package/tests/cypress/e2e/uat/entities/tasks/owner.cy.ts +191 -0
  137. package/tests/cypress/e2e/uat/roles/editor-role.bdd.md +552 -0
  138. package/tests/cypress/e2e/uat/roles/editor-role.cy.ts +210 -0
  139. package/tests/cypress/e2e/uat/roles/member-restrictions.bdd.md +450 -0
  140. package/tests/cypress/e2e/uat/roles/member-restrictions.cy.ts +189 -0
  141. package/tests/cypress/e2e/uat/roles/owner-full-crud.bdd.md +530 -0
  142. package/tests/cypress/e2e/uat/roles/owner-full-crud.cy.ts +247 -0
  143. package/tests/cypress/e2e/uat/scheduled-actions/devtools-ui.bdd.md +736 -0
  144. package/tests/cypress/e2e/uat/scheduled-actions/devtools-ui.cy.ts +740 -0
  145. package/tests/cypress/e2e/uat/teams/roles-matrix.bdd.md +553 -0
  146. package/tests/cypress/e2e/uat/teams/roles-matrix.cy.ts +185 -0
  147. package/tests/cypress/e2e/uat/teams/switcher.bdd.md +1151 -0
  148. package/tests/cypress/e2e/uat/teams/switcher.cy.ts +497 -0
  149. package/tests/cypress/e2e/uat/teams/team-switcher.md +198 -0
  150. package/tests/cypress/fixtures/blocks.json +218 -0
  151. package/tests/cypress/fixtures/entities.json +78 -0
  152. package/tests/cypress/fixtures/page-builder.json +21 -0
  153. package/tests/cypress/src/components/CategoriesPOM.ts +382 -0
  154. package/tests/cypress/src/components/CustomersPOM.ts +439 -0
  155. package/tests/cypress/src/components/DevKeyringPOM.ts +160 -0
  156. package/tests/cypress/src/components/EntityForm.ts +375 -0
  157. package/tests/cypress/src/components/EntityList.ts +389 -0
  158. package/tests/cypress/src/components/PageBuilderPOM.ts +710 -0
  159. package/tests/cypress/src/components/PostEditorPOM.ts +370 -0
  160. package/tests/cypress/src/components/PostsListPOM.ts +223 -0
  161. package/tests/cypress/src/components/PublicPagePOM.ts +447 -0
  162. package/tests/cypress/src/components/PublicPostPOM.ts +146 -0
  163. package/tests/cypress/src/components/TasksPOM.ts +272 -0
  164. package/tests/cypress/src/components/TeamSwitcherPOM.ts +450 -0
  165. package/tests/cypress/src/components/index.ts +21 -0
  166. package/tests/cypress/src/controllers/ApiKeysAPIController.js +178 -0
  167. package/tests/cypress/src/controllers/BaseAPIController.js +317 -0
  168. package/tests/cypress/src/controllers/CustomerAPIController.js +251 -0
  169. package/tests/cypress/src/controllers/PagesAPIController.js +226 -0
  170. package/tests/cypress/src/controllers/PostsAPIController.js +250 -0
  171. package/tests/cypress/src/controllers/TaskAPIController.js +240 -0
  172. package/tests/cypress/src/controllers/UsersAPIController.js +242 -0
  173. package/tests/cypress/src/controllers/index.js +25 -0
  174. package/tests/cypress/src/core/AuthPOM.ts +450 -0
  175. package/tests/cypress/src/core/BasePOM.ts +86 -0
  176. package/tests/cypress/src/core/BlockEditorBasePOM.ts +576 -0
  177. package/tests/cypress/src/core/DashboardEntityPOM.ts +692 -0
  178. package/tests/cypress/src/core/index.ts +14 -0
  179. package/tests/cypress/src/entities/CustomersPOM.ts +172 -0
  180. package/tests/cypress/src/entities/PagesPOM.ts +137 -0
  181. package/tests/cypress/src/entities/PostsPOM.ts +137 -0
  182. package/tests/cypress/src/entities/TasksPOM.ts +176 -0
  183. package/tests/cypress/src/entities/index.ts +14 -0
  184. package/tests/cypress/src/features/BillingPOM.ts +385 -0
  185. package/tests/cypress/src/features/DashboardPOM.ts +245 -0
  186. package/tests/cypress/src/features/DevtoolsPOM.ts +739 -0
  187. package/tests/cypress/src/features/PageBuilderPOM.ts +263 -0
  188. package/tests/cypress/src/features/PostEditorPOM.ts +313 -0
  189. package/tests/cypress/src/features/ScheduledActionsPOM.ts +463 -0
  190. package/tests/cypress/src/features/SettingsPOM.ts +362 -0
  191. package/tests/cypress/src/features/SuperadminPOM.ts +331 -0
  192. package/tests/cypress/src/features/SuperadminTeamRolesPOM.ts +285 -0
  193. package/tests/cypress/src/features/index.ts +28 -0
  194. package/tests/cypress/src/helpers/ApiInterceptor.ts +177 -0
  195. package/tests/cypress/src/index.ts +101 -0
  196. package/tests/cypress/src/pages/dashboard/Dashboard.js +677 -0
  197. package/tests/cypress/src/pages/dashboard/DashboardPage.js +43 -0
  198. package/tests/cypress/src/pages/dashboard/DashboardStats.js +546 -0
  199. package/tests/cypress/src/pages/dashboard/index.js +6 -0
  200. package/tests/cypress/src/pages/index.js +5 -0
  201. package/tests/cypress/src/pages/public/FeaturesPage.js +28 -0
  202. package/tests/cypress/src/pages/public/LandingPage.js +69 -0
  203. package/tests/cypress/src/pages/public/PricingPage.js +33 -0
  204. package/tests/cypress/src/pages/public/index.js +6 -0
  205. package/tests/cypress/src/selectors.ts +46 -0
  206. package/tests/cypress/src/session-helpers.ts +500 -0
  207. package/tests/cypress/support/doc-commands.ts +260 -0
  208. package/tests/cypress.config.ts +150 -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
  222. package/LICENSE +0 -21
@@ -0,0 +1,709 @@
1
+ /// <reference types="cypress" />
2
+
3
+ /**
4
+ * Posts API - CRUD Tests
5
+ *
6
+ * Comprehensive test suite for Posts API endpoints.
7
+ * Tests GET, POST, PUT, DELETE operations with taxonomy relations.
8
+ *
9
+ * Entity characteristics:
10
+ * - Required fields: slug, title, blocks, locale
11
+ * - Optional fields: excerpt, featuredImage, SEO fields, categoryIds
12
+ * - Unique constraint: slug + locale
13
+ * - Relations: post_taxonomy_relations (many-to-many with taxonomies)
14
+ * - Access: authenticated users only (dual auth: session + API key)
15
+ *
16
+ * Tags: @api, @feat-posts, @crud, @regression
17
+ */
18
+
19
+ import * as allure from 'allure-cypress'
20
+
21
+ const PostsAPIController = require('../../src/controllers/PostsAPIController.js')
22
+
23
+ describe('Posts API - CRUD Operations', {
24
+ tags: ['@api', '@feat-posts', '@crud', '@regression']
25
+ }, () => {
26
+ // Test constants
27
+ const SUPERADMIN_API_KEY = 'test_api_key_for_testing_purposes_only_not_a_real_secret_key_abc123'
28
+ const TEAM_ID = 'team-tmt-001'
29
+ const BASE_URL = Cypress.config('baseUrl') || 'http://localhost:5173'
30
+
31
+ // Controller instance
32
+ let postsAPI: InstanceType<typeof PostsAPIController>
33
+
34
+ // Track created posts for cleanup
35
+ let createdPosts: string[] = []
36
+
37
+ before(() => {
38
+ // Initialize controller with superadmin credentials and team context
39
+ postsAPI = new PostsAPIController(BASE_URL, SUPERADMIN_API_KEY, TEAM_ID)
40
+ cy.log('PostsAPIController initialized')
41
+ cy.log(`Base URL: ${BASE_URL}`)
42
+ cy.log(`Team ID: ${TEAM_ID}`)
43
+ })
44
+
45
+ beforeEach(() => {
46
+ allure.epic('API')
47
+ allure.feature('Posts')
48
+ })
49
+
50
+ afterEach(() => {
51
+ // Cleanup: Delete posts created during tests
52
+ createdPosts.forEach((postId) => {
53
+ postsAPI.deletePost(postId)
54
+ })
55
+ createdPosts = []
56
+ })
57
+
58
+ // ============================================================
59
+ // GET /api/v1/posts - List Posts
60
+ // ============================================================
61
+ describe('GET /api/v1/posts - List Posts', () => {
62
+ it('POST_API_001: Should list posts with valid API key', { tags: '@smoke' }, () => {
63
+ allure.story('CRUD Operations')
64
+ allure.severity('critical')
65
+
66
+ postsAPI.getPosts().then((response: any) => {
67
+ postsAPI.validateSuccessResponse(response, 200)
68
+ postsAPI.validatePaginatedResponse(response)
69
+ expect(response.body.data).to.be.an('array')
70
+
71
+ cy.log(`Found ${response.body.data.length} posts`)
72
+ })
73
+ })
74
+
75
+ it('POST_API_002: Should return post with expected structure', () => {
76
+ postsAPI.getPosts().then((response: any) => {
77
+ postsAPI.validateSuccessResponse(response, 200)
78
+
79
+ if (response.body.data.length > 0) {
80
+ const post = response.body.data[0]
81
+
82
+ // Verify post structure
83
+ expect(post).to.have.property('id')
84
+ expect(post).to.have.property('slug')
85
+ expect(post).to.have.property('title')
86
+ expect(post).to.have.property('locale')
87
+ expect(post).to.have.property('blocks')
88
+ expect(post.blocks).to.be.an('array')
89
+
90
+ // Categories relation (if present)
91
+ if (post.hasOwnProperty('categories')) {
92
+ expect(post.categories).to.be.an('array')
93
+ }
94
+
95
+ cy.log(`Post structure verified: ${post.title}`)
96
+ }
97
+ })
98
+ })
99
+
100
+ it('POST_API_003: Should paginate posts', () => {
101
+ postsAPI.getPosts({ page: 1, limit: 5 }).then((response: any) => {
102
+ postsAPI.validatePaginatedResponse(response)
103
+ expect(response.body.info.page).to.eq(1)
104
+ expect(response.body.info.limit).to.eq(5)
105
+ expect(response.body.data.length).to.be.at.most(5)
106
+
107
+ cy.log(`Page 1 with limit 5: ${response.body.data.length} posts`)
108
+ })
109
+ })
110
+
111
+ it('POST_API_004: Should filter posts by published status', () => {
112
+ postsAPI.getPosts({ published: true }).then((response: any) => {
113
+ postsAPI.validateSuccessResponse(response, 200)
114
+ expect(response.body.data).to.be.an('array')
115
+
116
+ // All returned posts should be published
117
+ response.body.data.forEach((post: any) => {
118
+ expect(post.published).to.be.true
119
+ })
120
+
121
+ cy.log(`Found ${response.body.data.length} published posts`)
122
+ })
123
+ })
124
+
125
+ it('POST_API_005: Should filter posts by locale', () => {
126
+ postsAPI.getPosts({ locale: 'en' }).then((response: any) => {
127
+ postsAPI.validateSuccessResponse(response, 200)
128
+ expect(response.body.data).to.be.an('array')
129
+
130
+ // All returned posts should have locale 'en'
131
+ response.body.data.forEach((post: any) => {
132
+ expect(post.locale).to.eq('en')
133
+ })
134
+
135
+ cy.log(`Found ${response.body.data.length} posts in 'en' locale`)
136
+ })
137
+ })
138
+
139
+ it('POST_API_006: Should search posts by title/excerpt', () => {
140
+ // Create a post with unique searchable term
141
+ const uniqueTerm = `SearchPost${Date.now()}`
142
+ const postData = PostsAPIController.generateRandomPostData({
143
+ title: `Post ${uniqueTerm} Title`,
144
+ excerpt: 'Test excerpt'
145
+ })
146
+
147
+ postsAPI.createPost(postData).then((createResponse: any) => {
148
+ postsAPI.validateSuccessResponse(createResponse, 201)
149
+ createdPosts.push(createResponse.body.data.id)
150
+
151
+ // Search for the unique term
152
+ postsAPI.getPosts({ search: uniqueTerm }).then((response: any) => {
153
+ postsAPI.validateSuccessResponse(response, 200)
154
+ expect(response.body.data).to.be.an('array')
155
+ expect(response.body.data.length).to.be.greaterThan(0)
156
+
157
+ const foundPost = response.body.data.find(
158
+ (p: any) => p.id === createResponse.body.data.id
159
+ )
160
+ expect(foundPost).to.exist
161
+ expect(foundPost.title).to.include(uniqueTerm)
162
+
163
+ cy.log(`Search found ${response.body.data.length} posts matching '${uniqueTerm}'`)
164
+ })
165
+ })
166
+ })
167
+
168
+ it('POST_API_007: Should reject request without authentication', { tags: '@security' }, () => {
169
+ // Create controller without API key
170
+ const noAuthAPI = new PostsAPIController(BASE_URL, null, TEAM_ID)
171
+
172
+ noAuthAPI.getPosts().then((response: any) => {
173
+ expect(response.status).to.eq(401)
174
+ expect(response.body.success).to.be.false
175
+
176
+ cy.log('Request without API key rejected with 401')
177
+ })
178
+ })
179
+
180
+ it('POST_API_008: Should reject request without x-team-id', () => {
181
+ // Create controller without team ID
182
+ const noTeamAPI = new PostsAPIController(BASE_URL, SUPERADMIN_API_KEY, null)
183
+
184
+ noTeamAPI.getPosts().then((response: any) => {
185
+ expect(response.status).to.eq(400)
186
+ expect(response.body.success).to.be.false
187
+ expect(response.body.code).to.eq('TEAM_CONTEXT_REQUIRED')
188
+
189
+ cy.log('Request without x-team-id rejected with TEAM_CONTEXT_REQUIRED')
190
+ })
191
+ })
192
+ })
193
+
194
+ // ============================================================
195
+ // POST /api/v1/posts - Create Post
196
+ // ============================================================
197
+ describe('POST /api/v1/posts - Create Post', () => {
198
+ it('POST_API_010: Should create post with valid data', { tags: '@smoke' }, () => {
199
+ allure.story('CRUD Operations')
200
+ allure.severity('critical')
201
+
202
+ const postData = PostsAPIController.generateRandomPostData({
203
+ title: 'TestCreate - Full Post',
204
+ excerpt: 'This is a test post excerpt',
205
+ featuredImage: '/images/test-featured.jpg',
206
+ blocks: [
207
+ {
208
+ id: `block-${Date.now()}`,
209
+ blockSlug: 'hero',
210
+ props: {
211
+ title: 'Test Hero',
212
+ subtitle: 'Test Subtitle'
213
+ }
214
+ }
215
+ ]
216
+ })
217
+
218
+ postsAPI.createPost(postData).then((response: any) => {
219
+ postsAPI.validateSuccessResponse(response, 201)
220
+ postsAPI.validatePostObject(response.body.data)
221
+
222
+ const post = response.body.data
223
+ expect(post.slug).to.eq(postData.slug)
224
+ expect(post.title).to.eq(postData.title)
225
+ expect(post.excerpt).to.eq(postData.excerpt)
226
+ expect(post.featuredImage).to.eq(postData.featuredImage)
227
+ expect(post.locale).to.eq(postData.locale)
228
+ expect(post.blocks).to.have.length(1)
229
+
230
+ createdPosts.push(post.id)
231
+ cy.log(`Created post: ${post.title} (ID: ${post.id})`)
232
+ })
233
+ })
234
+
235
+ it('POST_API_011: Should create post with minimal data', () => {
236
+ const minimalData = PostsAPIController.generateRandomPostData({
237
+ title: `Minimal Post ${Date.now()}`
238
+ })
239
+ delete minimalData.excerpt
240
+ delete minimalData.categoryIds
241
+
242
+ postsAPI.createPost(minimalData).then((response: any) => {
243
+ postsAPI.validateSuccessResponse(response, 201)
244
+ postsAPI.validatePostObject(response.body.data)
245
+
246
+ const post = response.body.data
247
+ expect(post.slug).to.eq(minimalData.slug)
248
+ expect(post.title).to.eq(minimalData.title)
249
+ expect(post.locale).to.eq(minimalData.locale)
250
+
251
+ createdPosts.push(post.id)
252
+ cy.log(`Created minimal post: ${post.id}`)
253
+ })
254
+ })
255
+
256
+ it('POST_API_012: Should create post with SEO fields', () => {
257
+ const postData = PostsAPIController.generateRandomPostData({
258
+ title: `SEO Post ${Date.now()}`,
259
+ seoTitle: 'Custom SEO Title',
260
+ seoDescription: 'Custom SEO Description',
261
+ seoKeywords: 'test, seo, keywords',
262
+ ogImage: '/images/og-image.jpg',
263
+ noindex: false,
264
+ nofollow: false
265
+ })
266
+
267
+ postsAPI.createPost(postData).then((response: any) => {
268
+ postsAPI.validateSuccessResponse(response, 201)
269
+
270
+ const post = response.body.data
271
+ expect(post.seoTitle).to.eq(postData.seoTitle)
272
+ expect(post.seoDescription).to.eq(postData.seoDescription)
273
+ expect(post.seoKeywords).to.eq(postData.seoKeywords)
274
+ expect(post.ogImage).to.eq(postData.ogImage)
275
+
276
+ createdPosts.push(post.id)
277
+ cy.log(`Created post with SEO fields: ${post.id}`)
278
+ })
279
+ })
280
+
281
+ it('POST_API_013: Should reject creation without title', () => {
282
+ const invalidData = {
283
+ slug: `no-title-${Date.now()}`,
284
+ locale: 'en',
285
+ blocks: []
286
+ // Missing: title
287
+ }
288
+
289
+ postsAPI.createPost(invalidData).then((response: any) => {
290
+ postsAPI.validateErrorResponse(response, 400)
291
+
292
+ cy.log('Creation without title rejected with 400')
293
+ })
294
+ })
295
+
296
+ it('POST_API_014: Should reject creation without slug', () => {
297
+ const invalidData = {
298
+ title: 'Test Post',
299
+ locale: 'en',
300
+ blocks: []
301
+ // Missing: slug
302
+ }
303
+
304
+ postsAPI.createPost(invalidData).then((response: any) => {
305
+ postsAPI.validateErrorResponse(response, 400)
306
+
307
+ cy.log('Creation without slug rejected with 400')
308
+ })
309
+ })
310
+
311
+ it('POST_API_015: Should reject invalid slug format', () => {
312
+ const invalidData = {
313
+ slug: 'Invalid Slug With Spaces!',
314
+ title: 'Test Post',
315
+ locale: 'en',
316
+ blocks: []
317
+ }
318
+
319
+ postsAPI.createPost(invalidData).then((response: any) => {
320
+ postsAPI.validateErrorResponse(response, 400)
321
+
322
+ cy.log('Invalid slug format rejected with 400')
323
+ })
324
+ })
325
+
326
+ it('POST_API_016: Should reject duplicate slug in same locale', () => {
327
+ const uniqueSlug = `duplicate-slug-${Date.now()}`
328
+
329
+ // Create first post
330
+ const postData1 = PostsAPIController.generateRandomPostData({
331
+ slug: uniqueSlug,
332
+ title: 'First Post'
333
+ })
334
+
335
+ postsAPI.createPost(postData1).then((response1: any) => {
336
+ postsAPI.validateSuccessResponse(response1, 201)
337
+ createdPosts.push(response1.body.data.id)
338
+
339
+ // Try to create duplicate
340
+ const postData2 = PostsAPIController.generateRandomPostData({
341
+ slug: uniqueSlug, // Same slug
342
+ title: 'Second Post'
343
+ })
344
+
345
+ postsAPI.createPost(postData2).then((response2: any) => {
346
+ expect(response2.status).to.be.oneOf([400, 409])
347
+ expect(response2.body.success).to.be.false
348
+
349
+ cy.log('Duplicate slug in same locale rejected')
350
+ })
351
+ })
352
+ })
353
+
354
+ it('POST_API_017: Should allow same slug in different locale', () => {
355
+ const sharedSlug = `shared-slug-${Date.now()}`
356
+
357
+ // Create post in 'en' locale
358
+ const postDataEN = PostsAPIController.generateRandomPostData({
359
+ slug: sharedSlug,
360
+ title: 'English Post',
361
+ locale: 'en'
362
+ })
363
+
364
+ postsAPI.createPost(postDataEN).then((responseEN: any) => {
365
+ postsAPI.validateSuccessResponse(responseEN, 201)
366
+ createdPosts.push(responseEN.body.data.id)
367
+
368
+ // Create post with same slug in 'es' locale
369
+ const postDataES = PostsAPIController.generateRandomPostData({
370
+ slug: sharedSlug, // Same slug
371
+ title: 'Spanish Post',
372
+ locale: 'es' // Different locale
373
+ })
374
+
375
+ postsAPI.createPost(postDataES).then((responseES: any) => {
376
+ postsAPI.validateSuccessResponse(responseES, 201)
377
+ createdPosts.push(responseES.body.data.id)
378
+
379
+ cy.log('Same slug in different locale allowed')
380
+ })
381
+ })
382
+ })
383
+
384
+ it('POST_API_018: Should reject creation without authentication', { tags: '@security' }, () => {
385
+ const noAuthAPI = new PostsAPIController(BASE_URL, null, TEAM_ID)
386
+ const postData = PostsAPIController.generateRandomPostData()
387
+
388
+ noAuthAPI.createPost(postData).then((response: any) => {
389
+ expect(response.status).to.eq(401)
390
+ expect(response.body.success).to.be.false
391
+
392
+ cy.log('Creation without authentication rejected with 401')
393
+ })
394
+ })
395
+
396
+ it('POST_API_019: Should reject creation without x-team-id', () => {
397
+ const noTeamAPI = new PostsAPIController(BASE_URL, SUPERADMIN_API_KEY, null)
398
+ const postData = PostsAPIController.generateRandomPostData()
399
+
400
+ noTeamAPI.createPost(postData).then((response: any) => {
401
+ expect(response.status).to.eq(400)
402
+ expect(response.body.success).to.be.false
403
+ expect(response.body.code).to.eq('TEAM_CONTEXT_REQUIRED')
404
+
405
+ cy.log('Creation without x-team-id rejected with TEAM_CONTEXT_REQUIRED')
406
+ })
407
+ })
408
+ })
409
+
410
+ // ============================================================
411
+ // GET /api/v1/posts/{id} - Get Post by ID
412
+ // ============================================================
413
+ describe('GET /api/v1/posts/{id} - Get Post by ID', () => {
414
+ let testPostId: string
415
+
416
+ beforeEach(() => {
417
+ // Create a test post
418
+ const postData = PostsAPIController.generateRandomPostData({
419
+ title: `Test Get By ID ${Date.now()}`
420
+ })
421
+
422
+ postsAPI.createPost(postData).then((response: any) => {
423
+ testPostId = response.body.data.id
424
+ createdPosts.push(testPostId)
425
+ })
426
+ })
427
+
428
+ it('POST_API_020: Should get post by valid ID', () => {
429
+ cy.then(() => {
430
+ postsAPI.getPostById(testPostId).then((response: any) => {
431
+ postsAPI.validateSuccessResponse(response, 200)
432
+ postsAPI.validatePostObject(response.body.data)
433
+ expect(response.body.data.id).to.eq(testPostId)
434
+
435
+ cy.log(`Got post: ${response.body.data.title}`)
436
+ })
437
+ })
438
+ })
439
+
440
+ it('POST_API_021: Should return 404 for non-existent post', () => {
441
+ postsAPI.getPostById('00000000-0000-0000-0000-000000000000').then((response: any) => {
442
+ postsAPI.validateErrorResponse(response, 404)
443
+
444
+ cy.log('Non-existent post returns 404')
445
+ })
446
+ })
447
+
448
+ it('POST_API_022: Should return 404 for invalid UUID format', () => {
449
+ postsAPI.getPostById('invalid-uuid-format').then((response: any) => {
450
+ postsAPI.validateErrorResponse(response, 404)
451
+
452
+ cy.log('Invalid UUID format returns 404')
453
+ })
454
+ })
455
+ })
456
+
457
+ // ============================================================
458
+ // PUT /api/v1/posts/{id} - Update Post
459
+ // ============================================================
460
+ describe('PUT /api/v1/posts/{id} - Update Post', () => {
461
+ let testPostId: string
462
+
463
+ beforeEach(() => {
464
+ const postData = PostsAPIController.generateRandomPostData({
465
+ title: `Test Update ${Date.now()}`
466
+ })
467
+
468
+ postsAPI.createPost(postData).then((response: any) => {
469
+ testPostId = response.body.data.id
470
+ createdPosts.push(testPostId)
471
+ })
472
+ })
473
+
474
+ it('POST_API_030: Should update post title', () => {
475
+ const updateData = {
476
+ title: 'Updated Title'
477
+ }
478
+
479
+ cy.then(() => {
480
+ postsAPI.updatePost(testPostId, updateData).then((response: any) => {
481
+ postsAPI.validateSuccessResponse(response, 200)
482
+ expect(response.body.data.title).to.eq(updateData.title)
483
+
484
+ cy.log(`Updated post title to: ${updateData.title}`)
485
+ })
486
+ })
487
+ })
488
+
489
+ it('POST_API_031: Should update post slug', () => {
490
+ const updateData = {
491
+ slug: `updated-slug-${Date.now()}`
492
+ }
493
+
494
+ cy.then(() => {
495
+ postsAPI.updatePost(testPostId, updateData).then((response: any) => {
496
+ postsAPI.validateSuccessResponse(response, 200)
497
+ expect(response.body.data.slug).to.eq(updateData.slug)
498
+
499
+ cy.log(`Updated post slug to: ${updateData.slug}`)
500
+ })
501
+ })
502
+ })
503
+
504
+ it('POST_API_032: Should update post excerpt and featured image', () => {
505
+ const updateData = {
506
+ excerpt: 'Updated excerpt text',
507
+ featuredImage: '/images/updated-featured.jpg'
508
+ }
509
+
510
+ cy.then(() => {
511
+ postsAPI.updatePost(testPostId, updateData).then((response: any) => {
512
+ postsAPI.validateSuccessResponse(response, 200)
513
+ expect(response.body.data.excerpt).to.eq(updateData.excerpt)
514
+ expect(response.body.data.featuredImage).to.eq(updateData.featuredImage)
515
+
516
+ cy.log('Updated excerpt and featured image')
517
+ })
518
+ })
519
+ })
520
+
521
+ it('POST_API_033: Should update post blocks', () => {
522
+ const updateData = {
523
+ blocks: [
524
+ {
525
+ id: `block-${Date.now()}`,
526
+ blockSlug: 'hero',
527
+ props: {
528
+ title: 'New Hero Block'
529
+ }
530
+ }
531
+ ]
532
+ }
533
+
534
+ cy.then(() => {
535
+ postsAPI.updatePost(testPostId, updateData).then((response: any) => {
536
+ postsAPI.validateSuccessResponse(response, 200)
537
+ expect(response.body.data.blocks).to.have.length(1)
538
+ expect(response.body.data.blocks[0].blockSlug).to.eq('hero')
539
+
540
+ cy.log('Updated post blocks')
541
+ })
542
+ })
543
+ })
544
+
545
+ it('POST_API_034: Should publish post', () => {
546
+ const updateData = {
547
+ published: true
548
+ }
549
+
550
+ cy.then(() => {
551
+ postsAPI.updatePost(testPostId, updateData).then((response: any) => {
552
+ postsAPI.validateSuccessResponse(response, 200)
553
+ expect(response.body.data.published).to.be.true
554
+
555
+ cy.log('Post published successfully')
556
+ })
557
+ })
558
+ })
559
+
560
+ it('POST_API_035: Should update SEO fields', () => {
561
+ const updateData = {
562
+ seoTitle: 'Updated SEO Title',
563
+ seoDescription: 'Updated SEO Description',
564
+ noindex: true
565
+ }
566
+
567
+ cy.then(() => {
568
+ postsAPI.updatePost(testPostId, updateData).then((response: any) => {
569
+ postsAPI.validateSuccessResponse(response, 200)
570
+ expect(response.body.data.seoTitle).to.eq(updateData.seoTitle)
571
+ expect(response.body.data.seoDescription).to.eq(updateData.seoDescription)
572
+ expect(response.body.data.noindex).to.be.true
573
+
574
+ cy.log('Updated SEO fields')
575
+ })
576
+ })
577
+ })
578
+
579
+ it('POST_API_036: Should return 404 for non-existent post', () => {
580
+ postsAPI.updatePost('00000000-0000-0000-0000-000000000000', {
581
+ title: 'New Title'
582
+ }).then((response: any) => {
583
+ postsAPI.validateErrorResponse(response, 404)
584
+
585
+ cy.log('Update non-existent post returns 404')
586
+ })
587
+ })
588
+
589
+ it('POST_API_037: Should reject update without authentication', { tags: '@security' }, () => {
590
+ const noAuthAPI = new PostsAPIController(BASE_URL, null, TEAM_ID)
591
+
592
+ cy.then(() => {
593
+ noAuthAPI.updatePost(testPostId, { title: 'Unauthorized Update' }).then((response: any) => {
594
+ expect(response.status).to.eq(401)
595
+ expect(response.body.success).to.be.false
596
+
597
+ cy.log('Update without authentication rejected with 401')
598
+ })
599
+ })
600
+ })
601
+ })
602
+
603
+ // ============================================================
604
+ // DELETE /api/v1/posts/{id} - Delete Post
605
+ // ============================================================
606
+ describe('DELETE /api/v1/posts/{id} - Delete Post', () => {
607
+ it('POST_API_040: Should delete post by valid ID', () => {
608
+ // Create post to delete
609
+ const postData = PostsAPIController.generateRandomPostData({
610
+ title: `Test Delete ${Date.now()}`
611
+ })
612
+
613
+ postsAPI.createPost(postData).then((createResponse: any) => {
614
+ const postId = createResponse.body.data.id
615
+
616
+ // Delete the post
617
+ postsAPI.deletePost(postId).then((deleteResponse: any) => {
618
+ postsAPI.validateSuccessResponse(deleteResponse, 200)
619
+
620
+ // Verify deletion
621
+ postsAPI.getPostById(postId).then((getResponse: any) => {
622
+ expect(getResponse.status).to.eq(404)
623
+ cy.log(`Deleted and verified: ${postId}`)
624
+ })
625
+ })
626
+ })
627
+ })
628
+
629
+ it('POST_API_041: Should return 404 for non-existent post', () => {
630
+ postsAPI.deletePost('00000000-0000-0000-0000-000000000000').then((response: any) => {
631
+ postsAPI.validateErrorResponse(response, 404)
632
+
633
+ cy.log('Delete non-existent post returns 404')
634
+ })
635
+ })
636
+
637
+ it('POST_API_042: Should reject deletion without authentication', { tags: '@security' }, () => {
638
+ // Create a post first
639
+ const postData = PostsAPIController.generateRandomPostData()
640
+
641
+ postsAPI.createPost(postData).then((createResponse: any) => {
642
+ const postId = createResponse.body.data.id
643
+ createdPosts.push(postId)
644
+
645
+ // Try to delete without auth
646
+ const noAuthAPI = new PostsAPIController(BASE_URL, null, TEAM_ID)
647
+ noAuthAPI.deletePost(postId).then((response: any) => {
648
+ expect(response.status).to.eq(401)
649
+ expect(response.body.success).to.be.false
650
+
651
+ cy.log('Deletion without authentication rejected with 401')
652
+ })
653
+ })
654
+ })
655
+ })
656
+
657
+ // ============================================================
658
+ // Integration Test - Complete CRUD Lifecycle
659
+ // ============================================================
660
+ describe('Integration - Complete CRUD Lifecycle', () => {
661
+ it('POST_API_100: Should complete full lifecycle: Create -> Read -> Update -> Delete', () => {
662
+ const postData = PostsAPIController.generateRandomPostData({
663
+ title: 'Lifecycle Test Post',
664
+ excerpt: 'Lifecycle test excerpt'
665
+ })
666
+
667
+ // 1. CREATE
668
+ postsAPI.createPost(postData).then((createResponse: any) => {
669
+ postsAPI.validateSuccessResponse(createResponse, 201)
670
+ const createdPost = createResponse.body.data
671
+ cy.log(`1. Created post: ${createdPost.id}`)
672
+
673
+ // 2. READ
674
+ postsAPI.getPostById(createdPost.id).then((readResponse: any) => {
675
+ postsAPI.validateSuccessResponse(readResponse, 200)
676
+ expect(readResponse.body.data.title).to.eq(postData.title)
677
+ cy.log(`2. Read post: ${createdPost.id}`)
678
+
679
+ // 3. UPDATE
680
+ const updateData = {
681
+ title: 'Updated Lifecycle Post',
682
+ published: true,
683
+ excerpt: 'Updated excerpt'
684
+ }
685
+ postsAPI.updatePost(createdPost.id, updateData).then((updateResponse: any) => {
686
+ postsAPI.validateSuccessResponse(updateResponse, 200)
687
+ expect(updateResponse.body.data.title).to.eq(updateData.title)
688
+ expect(updateResponse.body.data.published).to.be.true
689
+ expect(updateResponse.body.data.excerpt).to.eq(updateData.excerpt)
690
+ cy.log(`3. Updated post: ${updateData.title}`)
691
+
692
+ // 4. DELETE
693
+ postsAPI.deletePost(createdPost.id).then((deleteResponse: any) => {
694
+ postsAPI.validateSuccessResponse(deleteResponse, 200)
695
+ cy.log(`4. Deleted post: ${createdPost.id}`)
696
+
697
+ // 5. VERIFY DELETION
698
+ postsAPI.getPostById(createdPost.id).then((finalResponse: any) => {
699
+ expect(finalResponse.status).to.eq(404)
700
+ cy.log('5. Verified deletion: post not found (404)')
701
+ cy.log('Full CRUD lifecycle completed successfully')
702
+ })
703
+ })
704
+ })
705
+ })
706
+ })
707
+ })
708
+ })
709
+ })