@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.
- package/package.json +1 -1
- package/tests/cypress/e2e/_devtools/access.bdd.md +262 -0
- package/tests/cypress/e2e/_devtools/access.cy.ts +171 -0
- package/tests/cypress/e2e/_devtools/navigation.bdd.md +261 -0
- package/tests/cypress/e2e/_devtools/navigation.cy.ts +157 -0
- package/tests/cypress/e2e/_devtools/pages.bdd.md +303 -0
- package/tests/cypress/e2e/_devtools/pages.cy.ts +184 -0
- package/tests/cypress/e2e/_docs/README.md +215 -0
- package/tests/cypress/e2e/_docs/tutorials/sector7-superadmin-teams.narration.json +155 -0
- package/tests/cypress/e2e/_docs/tutorials/sector7-superadmin.cy.ts +390 -0
- package/tests/cypress/e2e/_docs/tutorials/teams-system.doc.cy.ts +349 -0
- package/tests/cypress/e2e/_docs/tutorials/teams-system.narration.json +165 -0
- package/tests/cypress/e2e/_selectors/auth.cy.ts +306 -0
- package/tests/cypress/e2e/_selectors/billing.cy.ts +89 -0
- package/tests/cypress/e2e/_selectors/dashboard-mobile.cy.ts +113 -0
- package/tests/cypress/e2e/_selectors/dashboard-navigation.cy.ts +89 -0
- package/tests/cypress/e2e/_selectors/dashboard-sidebar.cy.ts +60 -0
- package/tests/cypress/e2e/_selectors/dashboard-topnav.cy.ts +146 -0
- package/tests/cypress/e2e/_selectors/devtools.cy.ts +210 -0
- package/tests/cypress/e2e/_selectors/global-search.cy.ts +88 -0
- package/tests/cypress/e2e/_selectors/pages-editor.cy.ts +179 -0
- package/tests/cypress/e2e/_selectors/posts-editor.cy.ts +282 -0
- package/tests/cypress/e2e/_selectors/public.cy.ts +112 -0
- package/tests/cypress/e2e/_selectors/settings-api-keys.cy.ts +228 -0
- package/tests/cypress/e2e/_selectors/settings-billing.cy.ts +105 -0
- package/tests/cypress/e2e/_selectors/settings-layout.cy.ts +119 -0
- package/tests/cypress/e2e/_selectors/settings-password.cy.ts +71 -0
- package/tests/cypress/e2e/_selectors/settings-profile.cy.ts +82 -0
- package/tests/cypress/e2e/_selectors/settings-teams.cy.ts +68 -0
- package/tests/cypress/e2e/_selectors/superadmin.cy.ts +185 -0
- package/tests/cypress/e2e/_selectors/tasks.cy.ts +242 -0
- package/tests/cypress/e2e/_selectors/taxonomies.cy.ts +126 -0
- package/tests/cypress/e2e/_selectors/teams.cy.ts +142 -0
- package/tests/cypress/e2e/_superadmin/all-teams.bdd.md +261 -0
- package/tests/cypress/e2e/_superadmin/all-teams.cy.ts +177 -0
- package/tests/cypress/e2e/_superadmin/all-users.bdd.md +406 -0
- package/tests/cypress/e2e/_superadmin/all-users.cy.ts +294 -0
- package/tests/cypress/e2e/_superadmin/dashboard.bdd.md +235 -0
- package/tests/cypress/e2e/_superadmin/dashboard.cy.ts +149 -0
- package/tests/cypress/e2e/_superadmin/subscriptions-overview.bdd.md +290 -0
- package/tests/cypress/e2e/_superadmin/subscriptions-overview.cy.ts +194 -0
- package/tests/cypress/e2e/ai/ai-usage.cy.ts +209 -0
- package/tests/cypress/e2e/ai/chat-api.cy.ts +107 -0
- package/tests/cypress/e2e/ai/guardrails.cy.ts +332 -0
- package/tests/cypress/e2e/api/billing/BillingAPIController.js +319 -0
- package/tests/cypress/e2e/api/billing/check-action.cy.ts +326 -0
- package/tests/cypress/e2e/api/billing/checkout.cy.ts +358 -0
- package/tests/cypress/e2e/api/billing/lifecycle.cy.ts +423 -0
- package/tests/cypress/e2e/api/billing/plans/README.md +345 -0
- package/tests/cypress/e2e/api/billing/plans/business.cy.ts +412 -0
- package/tests/cypress/e2e/api/billing/plans/downgrade.cy.ts +510 -0
- package/tests/cypress/e2e/api/billing/plans/fixtures/billing-plans.json +163 -0
- package/tests/cypress/e2e/api/billing/plans/free.cy.ts +500 -0
- package/tests/cypress/e2e/api/billing/plans/pro.cy.ts +497 -0
- package/tests/cypress/e2e/api/billing/plans/starter.cy.ts +342 -0
- package/tests/cypress/e2e/api/billing/portal.cy.ts +313 -0
- package/tests/cypress/e2e/api/devtools/registries.bdd.md +300 -0
- package/tests/cypress/e2e/api/devtools/registries.cy.ts +368 -0
- package/tests/cypress/e2e/api/entities/blocks-scope.cy.ts +396 -0
- package/tests/cypress/e2e/api/entities/customers-crud.cy.ts +648 -0
- package/tests/cypress/e2e/api/entities/customers-metas.cy.ts +839 -0
- package/tests/cypress/e2e/api/entities/pages-crud.cy.ts +425 -0
- package/tests/cypress/e2e/api/entities/pages-status.cy.ts +335 -0
- package/tests/cypress/e2e/api/entities/post-categories-crud.cy.ts +610 -0
- package/tests/cypress/e2e/api/entities/posts-crud.cy.ts +709 -0
- package/tests/cypress/e2e/api/entities/posts-status.cy.ts +396 -0
- package/tests/cypress/e2e/api/entities/tasks-crud.cy.ts +602 -0
- package/tests/cypress/e2e/api/entities/tasks-metas.cy.ts +878 -0
- package/tests/cypress/e2e/api/entities/users-crud.cy.ts +469 -0
- package/tests/cypress/e2e/api/entities/users-metas.cy.ts +913 -0
- package/tests/cypress/e2e/api/entities/users-security.cy.ts +375 -0
- package/tests/cypress/e2e/api/scheduled-actions/cron-endpoint.bdd.md +375 -0
- package/tests/cypress/e2e/api/scheduled-actions/cron-endpoint.cy.ts +346 -0
- package/tests/cypress/e2e/api/scheduled-actions/devtools-endpoint.bdd.md +451 -0
- package/tests/cypress/e2e/api/scheduled-actions/devtools-endpoint.cy.ts +447 -0
- package/tests/cypress/e2e/api/scheduled-actions/scheduling.bdd.md +649 -0
- package/tests/cypress/e2e/api/scheduled-actions/scheduling.cy.ts +333 -0
- package/tests/cypress/e2e/api/settings/api-keys.crud.cy.ts +923 -0
- package/tests/cypress/e2e/uat/auth/app-roles/developer-login.bdd.md +231 -0
- package/tests/cypress/e2e/uat/auth/app-roles/developer-login.cy.ts +144 -0
- package/tests/cypress/e2e/uat/auth/app-roles/superadmin-login.bdd.md +118 -0
- package/tests/cypress/e2e/uat/auth/app-roles/superadmin-login.cy.ts +84 -0
- package/tests/cypress/e2e/uat/auth/custom-roles/editor-login.bdd.md +288 -0
- package/tests/cypress/e2e/uat/auth/custom-roles/editor-login.cy.ts +188 -0
- package/tests/cypress/e2e/uat/auth/login-logout.bdd.md +160 -0
- package/tests/cypress/e2e/uat/auth/login-logout.cy.ts +116 -0
- package/tests/cypress/e2e/uat/auth/password-reset.bdd.md +289 -0
- package/tests/cypress/e2e/uat/auth/password-reset.cy.ts +200 -0
- package/tests/cypress/e2e/uat/auth/team-roles/admin-login.bdd.md +225 -0
- package/tests/cypress/e2e/uat/auth/team-roles/admin-login.cy.ts +148 -0
- package/tests/cypress/e2e/uat/auth/team-roles/member-login.bdd.md +251 -0
- package/tests/cypress/e2e/uat/auth/team-roles/member-login.cy.ts +163 -0
- package/tests/cypress/e2e/uat/auth/team-roles/owner-login.bdd.md +231 -0
- package/tests/cypress/e2e/uat/auth/team-roles/owner-login.cy.ts +141 -0
- package/tests/cypress/e2e/uat/billing/extended.bdd.md +273 -0
- package/tests/cypress/e2e/uat/billing/extended.cy.ts +209 -0
- package/tests/cypress/e2e/uat/billing/feature-gates.bdd.md +407 -0
- package/tests/cypress/e2e/uat/billing/feature-gates.cy.ts +307 -0
- package/tests/cypress/e2e/uat/billing/page.bdd.md +329 -0
- package/tests/cypress/e2e/uat/billing/page.cy.ts +250 -0
- package/tests/cypress/e2e/uat/billing/status.bdd.md +190 -0
- package/tests/cypress/e2e/uat/billing/status.cy.ts +145 -0
- package/tests/cypress/e2e/uat/billing/team-switch.bdd.md +156 -0
- package/tests/cypress/e2e/uat/billing/team-switch.cy.ts +122 -0
- package/tests/cypress/e2e/uat/billing/usage.bdd.md +218 -0
- package/tests/cypress/e2e/uat/billing/usage.cy.ts +176 -0
- package/tests/cypress/e2e/uat/blocks/hero.bdd.md +124 -0
- package/tests/cypress/e2e/uat/blocks/hero.cy.ts +56 -0
- package/tests/cypress/e2e/uat/devtools/api-tester.cy.ts +390 -0
- package/tests/cypress/e2e/uat/entities/customers/member.bdd.md +275 -0
- package/tests/cypress/e2e/uat/entities/customers/member.cy.ts +122 -0
- package/tests/cypress/e2e/uat/entities/customers/owner.bdd.md +243 -0
- package/tests/cypress/e2e/uat/entities/customers/owner.cy.ts +165 -0
- package/tests/cypress/e2e/uat/entities/pages/block-crud.bdd.md +476 -0
- package/tests/cypress/e2e/uat/entities/pages/block-crud.cy.ts +486 -0
- package/tests/cypress/e2e/uat/entities/pages/block-editor.bdd.md +460 -0
- package/tests/cypress/e2e/uat/entities/pages/block-editor.cy.ts +301 -0
- package/tests/cypress/e2e/uat/entities/pages/list.bdd.md +432 -0
- package/tests/cypress/e2e/uat/entities/pages/list.cy.ts +273 -0
- package/tests/cypress/e2e/uat/entities/pages/public-rendering.bdd.md +696 -0
- package/tests/cypress/e2e/uat/entities/pages/public-rendering.cy.ts +340 -0
- package/tests/cypress/e2e/uat/entities/posts/categories-api-aware.bdd.md +161 -0
- package/tests/cypress/e2e/uat/entities/posts/categories-api-aware.cy.ts +104 -0
- package/tests/cypress/e2e/uat/entities/posts/categories.bdd.md +375 -0
- package/tests/cypress/e2e/uat/entities/posts/categories.cy.ts +241 -0
- package/tests/cypress/e2e/uat/entities/posts/editor.bdd.md +429 -0
- package/tests/cypress/e2e/uat/entities/posts/editor.cy.ts +257 -0
- package/tests/cypress/e2e/uat/entities/posts/list.bdd.md +340 -0
- package/tests/cypress/e2e/uat/entities/posts/list.cy.ts +177 -0
- package/tests/cypress/e2e/uat/entities/posts/public.bdd.md +614 -0
- package/tests/cypress/e2e/uat/entities/posts/public.cy.ts +249 -0
- package/tests/cypress/e2e/uat/entities/tasks/member.bdd.md +222 -0
- package/tests/cypress/e2e/uat/entities/tasks/member.cy.ts +165 -0
- package/tests/cypress/e2e/uat/entities/tasks/owner.bdd.md +419 -0
- package/tests/cypress/e2e/uat/entities/tasks/owner.cy.ts +191 -0
- package/tests/cypress/e2e/uat/roles/editor-role.bdd.md +552 -0
- package/tests/cypress/e2e/uat/roles/editor-role.cy.ts +210 -0
- package/tests/cypress/e2e/uat/roles/member-restrictions.bdd.md +450 -0
- package/tests/cypress/e2e/uat/roles/member-restrictions.cy.ts +189 -0
- package/tests/cypress/e2e/uat/roles/owner-full-crud.bdd.md +530 -0
- package/tests/cypress/e2e/uat/roles/owner-full-crud.cy.ts +247 -0
- package/tests/cypress/e2e/uat/scheduled-actions/devtools-ui.bdd.md +736 -0
- package/tests/cypress/e2e/uat/scheduled-actions/devtools-ui.cy.ts +740 -0
- package/tests/cypress/e2e/uat/teams/roles-matrix.bdd.md +553 -0
- package/tests/cypress/e2e/uat/teams/roles-matrix.cy.ts +185 -0
- package/tests/cypress/e2e/uat/teams/switcher.bdd.md +1151 -0
- package/tests/cypress/e2e/uat/teams/switcher.cy.ts +497 -0
- package/tests/cypress/e2e/uat/teams/team-switcher.md +198 -0
- package/tests/cypress/fixtures/blocks.json +218 -0
- package/tests/cypress/fixtures/entities.json +78 -0
- package/tests/cypress/fixtures/page-builder.json +21 -0
- package/tests/cypress/src/components/CategoriesPOM.ts +382 -0
- package/tests/cypress/src/components/CustomersPOM.ts +439 -0
- package/tests/cypress/src/components/DevKeyringPOM.ts +160 -0
- package/tests/cypress/src/components/EntityForm.ts +375 -0
- package/tests/cypress/src/components/EntityList.ts +389 -0
- package/tests/cypress/src/components/PageBuilderPOM.ts +710 -0
- package/tests/cypress/src/components/PostEditorPOM.ts +370 -0
- package/tests/cypress/src/components/PostsListPOM.ts +223 -0
- package/tests/cypress/src/components/PublicPagePOM.ts +447 -0
- package/tests/cypress/src/components/PublicPostPOM.ts +146 -0
- package/tests/cypress/src/components/TasksPOM.ts +272 -0
- package/tests/cypress/src/components/TeamSwitcherPOM.ts +450 -0
- package/tests/cypress/src/components/index.ts +21 -0
- package/tests/cypress/src/controllers/ApiKeysAPIController.js +178 -0
- package/tests/cypress/src/controllers/BaseAPIController.js +317 -0
- package/tests/cypress/src/controllers/CustomerAPIController.js +251 -0
- package/tests/cypress/src/controllers/PagesAPIController.js +226 -0
- package/tests/cypress/src/controllers/PostsAPIController.js +250 -0
- package/tests/cypress/src/controllers/TaskAPIController.js +240 -0
- package/tests/cypress/src/controllers/UsersAPIController.js +242 -0
- package/tests/cypress/src/controllers/index.js +25 -0
- package/tests/cypress/src/core/AuthPOM.ts +450 -0
- package/tests/cypress/src/core/BasePOM.ts +86 -0
- package/tests/cypress/src/core/BlockEditorBasePOM.ts +576 -0
- package/tests/cypress/src/core/DashboardEntityPOM.ts +692 -0
- package/tests/cypress/src/core/index.ts +14 -0
- package/tests/cypress/src/entities/CustomersPOM.ts +172 -0
- package/tests/cypress/src/entities/PagesPOM.ts +137 -0
- package/tests/cypress/src/entities/PostsPOM.ts +137 -0
- package/tests/cypress/src/entities/TasksPOM.ts +176 -0
- package/tests/cypress/src/entities/index.ts +14 -0
- package/tests/cypress/src/features/BillingPOM.ts +385 -0
- package/tests/cypress/src/features/DashboardPOM.ts +245 -0
- package/tests/cypress/src/features/DevtoolsPOM.ts +739 -0
- package/tests/cypress/src/features/PageBuilderPOM.ts +263 -0
- package/tests/cypress/src/features/PostEditorPOM.ts +313 -0
- package/tests/cypress/src/features/ScheduledActionsPOM.ts +463 -0
- package/tests/cypress/src/features/SettingsPOM.ts +362 -0
- package/tests/cypress/src/features/SuperadminPOM.ts +331 -0
- package/tests/cypress/src/features/SuperadminTeamRolesPOM.ts +285 -0
- package/tests/cypress/src/features/index.ts +28 -0
- package/tests/cypress/src/helpers/ApiInterceptor.ts +177 -0
- package/tests/cypress/src/index.ts +101 -0
- package/tests/cypress/src/pages/dashboard/Dashboard.js +677 -0
- package/tests/cypress/src/pages/dashboard/DashboardPage.js +43 -0
- package/tests/cypress/src/pages/dashboard/DashboardStats.js +546 -0
- package/tests/cypress/src/pages/dashboard/index.js +6 -0
- package/tests/cypress/src/pages/index.js +5 -0
- package/tests/cypress/src/pages/public/FeaturesPage.js +28 -0
- package/tests/cypress/src/pages/public/LandingPage.js +69 -0
- package/tests/cypress/src/pages/public/PricingPage.js +33 -0
- package/tests/cypress/src/pages/public/index.js +6 -0
- package/tests/cypress/src/selectors.ts +46 -0
- package/tests/cypress/src/session-helpers.ts +500 -0
- package/tests/cypress/support/doc-commands.ts +260 -0
- package/tests/cypress/support/e2e.ts +89 -0
- package/tests/cypress.config.ts +165 -0
- package/tests/jest/components/post-header.test.tsx +377 -0
- package/tests/jest/config/role-config.test.ts +529 -0
- package/tests/jest/jest.config.ts +81 -0
- package/tests/jest/langchain/COVERAGE.md +372 -0
- package/tests/jest/langchain/guardrails.test.ts +465 -0
- package/tests/jest/langchain/streaming.test.ts +367 -0
- package/tests/jest/langchain/token-tracker.test.ts +455 -0
- package/tests/jest/langchain/tracer-callbacks.test.ts +881 -0
- package/tests/jest/langchain/tracer.test.ts +823 -0
- package/tests/jest/user-roles/role-helpers.test.ts +432 -0
- package/tests/jest/validation/categories.test.ts +429 -0
- package/tests/jest/validation/posts.test.ts +546 -0
- package/tests/tsconfig.json +15 -0
|
@@ -0,0 +1,878 @@
|
|
|
1
|
+
/// <reference types="cypress" />
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Tasks API - Metadata Tests
|
|
5
|
+
*
|
|
6
|
+
* Comprehensive test suite for Task API endpoints with metadata functionality.
|
|
7
|
+
* Covers 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 TaskAPIController = require('../../../src/controllers/TaskAPIController.js')
|
|
14
|
+
|
|
15
|
+
describe('Tasks API - Metadata Operations', {
|
|
16
|
+
tags: ['@api', '@feat-tasks', '@metas', '@regression']
|
|
17
|
+
}, () => {
|
|
18
|
+
let taskAPI: any
|
|
19
|
+
let createdTasks: any[] = []
|
|
20
|
+
|
|
21
|
+
// Superadmin API key for testing
|
|
22
|
+
const SUPERADMIN_API_KEY = 'test_api_key_for_testing_purposes_only_not_a_real_secret_key_abc123'
|
|
23
|
+
const TEAM_ID = 'team-tmt-001'
|
|
24
|
+
const BASE_URL = Cypress.config('baseUrl') || 'http://localhost:5173'
|
|
25
|
+
|
|
26
|
+
before(() => {
|
|
27
|
+
// Initialize API controller with superadmin API key and team context
|
|
28
|
+
taskAPI = new TaskAPIController(BASE_URL, SUPERADMIN_API_KEY, TEAM_ID)
|
|
29
|
+
cy.log('TaskAPIController initialized for metadata tests')
|
|
30
|
+
cy.log(`Base URL: ${BASE_URL}`)
|
|
31
|
+
cy.log(`Team ID: ${TEAM_ID}`)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
allure.epic('API')
|
|
36
|
+
allure.feature('Tasks')
|
|
37
|
+
allure.story('Metadata Operations')
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
afterEach(() => {
|
|
41
|
+
// Cleanup: Delete tasks created during tests
|
|
42
|
+
if (createdTasks.length > 0) {
|
|
43
|
+
createdTasks.forEach((task: any) => {
|
|
44
|
+
if (task && task.id) {
|
|
45
|
+
taskAPI.deleteTask(task.id)
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
createdTasks = []
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
// ============================================================
|
|
53
|
+
// GET /api/v1/tasks - List Tasks with Metadata
|
|
54
|
+
// ============================================================
|
|
55
|
+
describe('GET /api/v1/tasks - List Tasks with Metadata', () => {
|
|
56
|
+
let taskWithMetas: any
|
|
57
|
+
|
|
58
|
+
before(() => {
|
|
59
|
+
// Create a task with metadata for list tests
|
|
60
|
+
const taskData = taskAPI.generateRandomTaskData({
|
|
61
|
+
title: 'Task with Metas for List',
|
|
62
|
+
metas: {
|
|
63
|
+
uiPreferences: taskAPI.generateSampleMetadata('uiPreferences').uiPreferences,
|
|
64
|
+
notifications: taskAPI.generateSampleMetadata('notifications').notifications
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
taskAPI.createTask(taskData).then((response: any) => {
|
|
69
|
+
taskWithMetas = response.body.data
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
after(() => {
|
|
74
|
+
if (taskWithMetas && taskWithMetas.id) {
|
|
75
|
+
taskAPI.deleteTask(taskWithMetas.id)
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('TASKS_META_001: Should list tasks without metas property when metas param not provided', () => {
|
|
80
|
+
taskAPI.getTasks().then((response: any) => {
|
|
81
|
+
taskAPI.validateSuccessResponse(response, 200)
|
|
82
|
+
|
|
83
|
+
// Tasks should NOT have metas property when not requested
|
|
84
|
+
response.body.data.forEach((task: any) => {
|
|
85
|
+
expect(task).to.not.have.property('metas')
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
cy.log('Verified: No metas property in response without metas param')
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('TASKS_META_002: Should list tasks with metas=all including all metadata groups', () => {
|
|
93
|
+
taskAPI.getTasks({ metas: 'all' }).then((response: any) => {
|
|
94
|
+
taskAPI.validateSuccessResponse(response, 200)
|
|
95
|
+
|
|
96
|
+
// Find our test task
|
|
97
|
+
const foundTask = response.body.data.find((t: any) => t.id === taskWithMetas.id)
|
|
98
|
+
if (foundTask) {
|
|
99
|
+
expect(foundTask).to.have.property('metas')
|
|
100
|
+
expect(foundTask.metas).to.be.an('object')
|
|
101
|
+
|
|
102
|
+
cy.log(`Task has metas: ${JSON.stringify(Object.keys(foundTask.metas))}`)
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('TASKS_META_003: Should list tasks with metas=key1 including only specified metaKey', () => {
|
|
108
|
+
taskAPI.getTasks({ metas: 'uiPreferences' }).then((response: any) => {
|
|
109
|
+
taskAPI.validateSuccessResponse(response, 200)
|
|
110
|
+
|
|
111
|
+
// Find our test task
|
|
112
|
+
const foundTask = response.body.data.find((t: any) => t.id === taskWithMetas.id)
|
|
113
|
+
if (foundTask && foundTask.metas) {
|
|
114
|
+
expect(foundTask).to.have.property('metas')
|
|
115
|
+
// Should only have uiPreferences key
|
|
116
|
+
if (Object.keys(foundTask.metas).length > 0) {
|
|
117
|
+
expect(foundTask.metas).to.have.property('uiPreferences')
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
cy.log('Verified: Only requested metaKey included')
|
|
122
|
+
})
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('TASKS_META_004: Should list tasks with metas=key1,key2 including multiple metaKeys', () => {
|
|
126
|
+
taskAPI.getTasks({ metas: 'uiPreferences,notifications' }).then((response: any) => {
|
|
127
|
+
taskAPI.validateSuccessResponse(response, 200)
|
|
128
|
+
|
|
129
|
+
// Find our test task
|
|
130
|
+
const foundTask = response.body.data.find((t: any) => t.id === taskWithMetas.id)
|
|
131
|
+
if (foundTask && foundTask.metas) {
|
|
132
|
+
expect(foundTask).to.have.property('metas')
|
|
133
|
+
// May have one or both keys
|
|
134
|
+
const metaKeys = Object.keys(foundTask.metas)
|
|
135
|
+
cy.log(`Task metas keys: ${metaKeys.join(', ')}`)
|
|
136
|
+
}
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it('TASKS_META_005: Should return empty metas object for non-existent metaKey', () => {
|
|
141
|
+
taskAPI.getTasks({ metas: 'nonExistentKey' }).then((response: any) => {
|
|
142
|
+
taskAPI.validateSuccessResponse(response, 200)
|
|
143
|
+
|
|
144
|
+
// Tasks should have metas property but it should be empty for this key
|
|
145
|
+
response.body.data.forEach((task: any) => {
|
|
146
|
+
if (task.metas) {
|
|
147
|
+
expect(task.metas).to.not.have.property('nonExistentKey')
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
cy.log('Verified: Non-existent metaKey returns empty metas')
|
|
152
|
+
})
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
it('TASKS_META_006: Should combine pagination with metas parameter', () => {
|
|
156
|
+
taskAPI.getTasks({ page: 1, limit: 5, metas: 'all' }).then((response: any) => {
|
|
157
|
+
taskAPI.validatePaginatedResponse(response)
|
|
158
|
+
expect(response.body.info.page).to.eq(1)
|
|
159
|
+
expect(response.body.info.limit).to.eq(5)
|
|
160
|
+
|
|
161
|
+
// Tasks with metas should have metas property
|
|
162
|
+
const tasksWithMetas = response.body.data.filter((t: any) => t.metas && Object.keys(t.metas).length > 0)
|
|
163
|
+
cy.log(`Found ${tasksWithMetas.length} tasks with metas (paginated)`)
|
|
164
|
+
})
|
|
165
|
+
})
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
// ============================================================
|
|
169
|
+
// GET /api/v1/tasks/{id} - Get Single Task with Metadata
|
|
170
|
+
// ============================================================
|
|
171
|
+
describe('GET /api/v1/tasks/{id} - Get Single Task with Metadata', () => {
|
|
172
|
+
let taskWithMetas: any
|
|
173
|
+
let taskWithoutMetas: any
|
|
174
|
+
|
|
175
|
+
beforeEach(() => {
|
|
176
|
+
// Create a task with metadata
|
|
177
|
+
const taskDataWithMetas = taskAPI.generateRandomTaskData({
|
|
178
|
+
title: 'Task with Metas',
|
|
179
|
+
metas: {
|
|
180
|
+
uiPreferences: taskAPI.generateSampleMetadata('uiPreferences').uiPreferences,
|
|
181
|
+
tracking: taskAPI.generateSampleMetadata('tracking').tracking
|
|
182
|
+
}
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
taskAPI.createTask(taskDataWithMetas).then((response: any) => {
|
|
186
|
+
taskWithMetas = response.body.data
|
|
187
|
+
createdTasks.push(taskWithMetas)
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
// Create a task without metadata
|
|
191
|
+
const taskDataWithoutMetas = taskAPI.generateRandomTaskData({
|
|
192
|
+
title: 'Task without Metas'
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
taskAPI.createTask(taskDataWithoutMetas).then((response: any) => {
|
|
196
|
+
taskWithoutMetas = response.body.data
|
|
197
|
+
createdTasks.push(taskWithoutMetas)
|
|
198
|
+
})
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
it('TASKS_META_010: Should get task without metas property when metas param not provided', () => {
|
|
202
|
+
cy.then(() => {
|
|
203
|
+
taskAPI.getTaskById(taskWithMetas.id).then((response: any) => {
|
|
204
|
+
taskAPI.validateSuccessResponse(response, 200)
|
|
205
|
+
expect(response.body.data).to.not.have.property('metas')
|
|
206
|
+
|
|
207
|
+
cy.log('Verified: No metas property without metas param')
|
|
208
|
+
})
|
|
209
|
+
})
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
it('TASKS_META_011: Should get task with metas=all including all metadata groups', () => {
|
|
213
|
+
cy.then(() => {
|
|
214
|
+
taskAPI.getTaskById(taskWithMetas.id, { metas: 'all' }).then((response: any) => {
|
|
215
|
+
taskAPI.validateSuccessResponse(response, 200)
|
|
216
|
+
expect(response.body.data).to.have.property('metas')
|
|
217
|
+
expect(response.body.data.metas).to.be.an('object')
|
|
218
|
+
|
|
219
|
+
// Should have the metas we created
|
|
220
|
+
expect(response.body.data.metas).to.have.property('uiPreferences')
|
|
221
|
+
expect(response.body.data.metas).to.have.property('tracking')
|
|
222
|
+
|
|
223
|
+
cy.log(`Task metas: ${JSON.stringify(Object.keys(response.body.data.metas))}`)
|
|
224
|
+
})
|
|
225
|
+
})
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
it('TASKS_META_012: Should get task with metas=key1 including only specified metaKey', () => {
|
|
229
|
+
cy.then(() => {
|
|
230
|
+
taskAPI.getTaskById(taskWithMetas.id, { metas: 'uiPreferences' }).then((response: any) => {
|
|
231
|
+
taskAPI.validateSuccessResponse(response, 200)
|
|
232
|
+
expect(response.body.data).to.have.property('metas')
|
|
233
|
+
|
|
234
|
+
// Should only have uiPreferences
|
|
235
|
+
expect(response.body.data.metas).to.have.property('uiPreferences')
|
|
236
|
+
expect(response.body.data.metas).to.not.have.property('tracking')
|
|
237
|
+
|
|
238
|
+
cy.log('Verified: Only uiPreferences metaKey returned')
|
|
239
|
+
})
|
|
240
|
+
})
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
it('TASKS_META_013: Should get task with metas=key1,key2 including multiple metaKeys', () => {
|
|
244
|
+
cy.then(() => {
|
|
245
|
+
taskAPI.getTaskById(taskWithMetas.id, { metas: 'uiPreferences,tracking' }).then((response: any) => {
|
|
246
|
+
taskAPI.validateSuccessResponse(response, 200)
|
|
247
|
+
expect(response.body.data).to.have.property('metas')
|
|
248
|
+
|
|
249
|
+
// Should have both keys
|
|
250
|
+
expect(response.body.data.metas).to.have.property('uiPreferences')
|
|
251
|
+
expect(response.body.data.metas).to.have.property('tracking')
|
|
252
|
+
|
|
253
|
+
cy.log('Verified: Both metaKeys returned')
|
|
254
|
+
})
|
|
255
|
+
})
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
it('TASKS_META_014: Should get task without metadata returning metas: {}', () => {
|
|
259
|
+
cy.then(() => {
|
|
260
|
+
taskAPI.getTaskById(taskWithoutMetas.id, { metas: 'all' }).then((response: any) => {
|
|
261
|
+
taskAPI.validateSuccessResponse(response, 200)
|
|
262
|
+
expect(response.body.data).to.have.property('metas')
|
|
263
|
+
expect(response.body.data.metas).to.deep.eq({})
|
|
264
|
+
|
|
265
|
+
cy.log('Verified: Task without metadata returns empty metas object')
|
|
266
|
+
})
|
|
267
|
+
})
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
it('TASKS_META_015: Should return empty metas for non-existent metaKey', () => {
|
|
271
|
+
cy.then(() => {
|
|
272
|
+
taskAPI.getTaskById(taskWithMetas.id, { metas: 'nonExistentKey' }).then((response: any) => {
|
|
273
|
+
taskAPI.validateSuccessResponse(response, 200)
|
|
274
|
+
expect(response.body.data).to.have.property('metas')
|
|
275
|
+
expect(response.body.data.metas).to.not.have.property('nonExistentKey')
|
|
276
|
+
|
|
277
|
+
cy.log('Verified: Non-existent metaKey not in response')
|
|
278
|
+
})
|
|
279
|
+
})
|
|
280
|
+
})
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
// ============================================================
|
|
284
|
+
// POST /api/v1/tasks - Create Task with Metadata
|
|
285
|
+
// ============================================================
|
|
286
|
+
describe('POST /api/v1/tasks - Create Task with Metadata', () => {
|
|
287
|
+
it('TASKS_META_020: Should create task without metas and no metas in response', () => {
|
|
288
|
+
const taskData = taskAPI.generateRandomTaskData({
|
|
289
|
+
title: 'Task without metas'
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
taskAPI.createTask(taskData).then((response: any) => {
|
|
293
|
+
taskAPI.validateSuccessResponse(response, 201)
|
|
294
|
+
expect(response.body.data).to.not.have.property('metas')
|
|
295
|
+
|
|
296
|
+
createdTasks.push(response.body.data)
|
|
297
|
+
cy.log('Verified: No metas in response when not provided')
|
|
298
|
+
})
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
it('TASKS_META_021: Should create task with one meta group and include in response', () => {
|
|
302
|
+
const taskData = taskAPI.generateRandomTaskData({
|
|
303
|
+
title: 'Task with one meta group',
|
|
304
|
+
metas: {
|
|
305
|
+
uiPreferences: {
|
|
306
|
+
colorLabel: 'green',
|
|
307
|
+
showInKanban: true
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
taskAPI.createTask(taskData).then((response: any) => {
|
|
313
|
+
taskAPI.validateSuccessResponse(response, 201)
|
|
314
|
+
expect(response.body.data).to.have.property('metas')
|
|
315
|
+
expect(response.body.data.metas).to.have.property('uiPreferences')
|
|
316
|
+
expect(response.body.data.metas.uiPreferences.colorLabel).to.eq('green')
|
|
317
|
+
|
|
318
|
+
createdTasks.push(response.body.data)
|
|
319
|
+
cy.log('Verified: Single meta group created and returned')
|
|
320
|
+
})
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
it('TASKS_META_022: Should create task with multiple meta groups', () => {
|
|
324
|
+
const taskData = taskAPI.generateRandomTaskData({
|
|
325
|
+
title: 'Task with multiple meta groups',
|
|
326
|
+
metas: {
|
|
327
|
+
uiPreferences: taskAPI.generateSampleMetadata('uiPreferences').uiPreferences,
|
|
328
|
+
notifications: taskAPI.generateSampleMetadata('notifications').notifications,
|
|
329
|
+
tracking: taskAPI.generateSampleMetadata('tracking').tracking
|
|
330
|
+
}
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
taskAPI.createTask(taskData).then((response: any) => {
|
|
334
|
+
taskAPI.validateSuccessResponse(response, 201)
|
|
335
|
+
expect(response.body.data).to.have.property('metas')
|
|
336
|
+
expect(response.body.data.metas).to.have.property('uiPreferences')
|
|
337
|
+
expect(response.body.data.metas).to.have.property('notifications')
|
|
338
|
+
expect(response.body.data.metas).to.have.property('tracking')
|
|
339
|
+
|
|
340
|
+
createdTasks.push(response.body.data)
|
|
341
|
+
cy.log('Verified: All meta groups created')
|
|
342
|
+
})
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
it('TASKS_META_023: Should create task with nested meta structure preserved', () => {
|
|
346
|
+
const nestedMetas = {
|
|
347
|
+
customFields: {
|
|
348
|
+
level1: {
|
|
349
|
+
level2: {
|
|
350
|
+
level3: {
|
|
351
|
+
deepValue: 'nested-test',
|
|
352
|
+
deepArray: [1, 2, 3]
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const taskData = taskAPI.generateRandomTaskData({
|
|
360
|
+
title: 'Task with nested metas',
|
|
361
|
+
metas: nestedMetas
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
taskAPI.createTask(taskData).then((response: any) => {
|
|
365
|
+
taskAPI.validateSuccessResponse(response, 201)
|
|
366
|
+
expect(response.body.data).to.have.property('metas')
|
|
367
|
+
expect(response.body.data.metas).to.have.property('customFields')
|
|
368
|
+
expect(response.body.data.metas.customFields.level1.level2.level3.deepValue).to.eq('nested-test')
|
|
369
|
+
expect(response.body.data.metas.customFields.level1.level2.level3.deepArray).to.deep.eq([1, 2, 3])
|
|
370
|
+
|
|
371
|
+
createdTasks.push(response.body.data)
|
|
372
|
+
cy.log('Verified: Nested structure preserved')
|
|
373
|
+
})
|
|
374
|
+
})
|
|
375
|
+
|
|
376
|
+
it('TASKS_META_024: Should reject creation with only metas and no title', () => {
|
|
377
|
+
const taskData = {
|
|
378
|
+
metas: {
|
|
379
|
+
uiPreferences: {
|
|
380
|
+
colorLabel: 'red'
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
taskAPI.createTask(taskData).then((response: any) => {
|
|
386
|
+
expect(response.status).to.eq(400)
|
|
387
|
+
expect(response.body.success).to.be.false
|
|
388
|
+
|
|
389
|
+
cy.log('Verified: Cannot create task with only metas')
|
|
390
|
+
})
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
it('TASKS_META_025: Should handle invalid metas format (string instead of object)', () => {
|
|
394
|
+
const taskData = taskAPI.generateRandomTaskData({
|
|
395
|
+
title: 'Task with invalid metas',
|
|
396
|
+
metas: 'invalid-string-metas'
|
|
397
|
+
})
|
|
398
|
+
|
|
399
|
+
taskAPI.createTask(taskData).then((response: any) => {
|
|
400
|
+
// API should either reject or ignore invalid metas
|
|
401
|
+
if (response.status === 201) {
|
|
402
|
+
// If created, metas should not be the string
|
|
403
|
+
expect(response.body.data.metas).to.not.eq('invalid-string-metas')
|
|
404
|
+
createdTasks.push(response.body.data)
|
|
405
|
+
} else {
|
|
406
|
+
expect(response.status).to.eq(400)
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
cy.log('Verified: Invalid metas format handled')
|
|
410
|
+
})
|
|
411
|
+
})
|
|
412
|
+
})
|
|
413
|
+
|
|
414
|
+
// ============================================================
|
|
415
|
+
// PATCH /api/v1/tasks/{id} - Update Metadata
|
|
416
|
+
// ============================================================
|
|
417
|
+
describe('PATCH /api/v1/tasks/{id} - Update Metadata', () => {
|
|
418
|
+
let testTask: any
|
|
419
|
+
|
|
420
|
+
beforeEach(() => {
|
|
421
|
+
// Create a task with initial metadata
|
|
422
|
+
const taskData = taskAPI.generateRandomTaskData({
|
|
423
|
+
title: 'Task for Meta Updates',
|
|
424
|
+
metas: {
|
|
425
|
+
uiPreferences: {
|
|
426
|
+
theme: 'dark',
|
|
427
|
+
sidebar: true,
|
|
428
|
+
language: 'en'
|
|
429
|
+
},
|
|
430
|
+
notifications: {
|
|
431
|
+
emailOnDue: true,
|
|
432
|
+
reminderDays: 3
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
})
|
|
436
|
+
|
|
437
|
+
taskAPI.createTask(taskData).then((response: any) => {
|
|
438
|
+
testTask = response.body.data
|
|
439
|
+
createdTasks.push(testTask)
|
|
440
|
+
})
|
|
441
|
+
})
|
|
442
|
+
|
|
443
|
+
it('TASKS_META_030: Should update only task data without affecting metas', () => {
|
|
444
|
+
cy.then(() => {
|
|
445
|
+
const updateData = {
|
|
446
|
+
title: 'Updated Title Only'
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
taskAPI.updateTask(testTask.id, updateData).then((response: any) => {
|
|
450
|
+
taskAPI.validateSuccessResponse(response, 200)
|
|
451
|
+
expect(response.body.data.title).to.eq(updateData.title)
|
|
452
|
+
// When updating without metas, response should not include metas
|
|
453
|
+
expect(response.body.data).to.not.have.property('metas')
|
|
454
|
+
|
|
455
|
+
// Verify metas still exist by fetching with metas=all
|
|
456
|
+
taskAPI.getTaskById(testTask.id, { metas: 'all' }).then((getResponse: any) => {
|
|
457
|
+
expect(getResponse.body.data.metas).to.have.property('uiPreferences')
|
|
458
|
+
cy.log('Verified: Metas unchanged when not included in update')
|
|
459
|
+
})
|
|
460
|
+
})
|
|
461
|
+
})
|
|
462
|
+
})
|
|
463
|
+
|
|
464
|
+
it('TASKS_META_031: Should update both task data and metas', () => {
|
|
465
|
+
cy.then(() => {
|
|
466
|
+
const updateData = {
|
|
467
|
+
title: 'Updated with Metas',
|
|
468
|
+
metas: {
|
|
469
|
+
uiPreferences: {
|
|
470
|
+
theme: 'light'
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
taskAPI.updateTask(testTask.id, updateData).then((response: any) => {
|
|
476
|
+
taskAPI.validateSuccessResponse(response, 200)
|
|
477
|
+
expect(response.body.data.title).to.eq(updateData.title)
|
|
478
|
+
expect(response.body.data).to.have.property('metas')
|
|
479
|
+
expect(response.body.data.metas.uiPreferences.theme).to.eq('light')
|
|
480
|
+
|
|
481
|
+
cy.log('Verified: Both task data and metas updated')
|
|
482
|
+
})
|
|
483
|
+
})
|
|
484
|
+
})
|
|
485
|
+
|
|
486
|
+
it('TASKS_META_032: Should support metas-only updates without entity fields', () => {
|
|
487
|
+
/**
|
|
488
|
+
* NOTE: The generic entity API now supports metas-only updates.
|
|
489
|
+
* Previously this required at least one entity field, but the API
|
|
490
|
+
* has been updated to allow updating only metas.
|
|
491
|
+
*/
|
|
492
|
+
cy.then(() => {
|
|
493
|
+
const updateData = {
|
|
494
|
+
metas: {
|
|
495
|
+
uiPreferences: {
|
|
496
|
+
newSetting: 'added-via-metas-only'
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Generic API now supports metas-only updates
|
|
502
|
+
taskAPI.updateTask(testTask.id, updateData).then((response: any) => {
|
|
503
|
+
taskAPI.validateSuccessResponse(response, 200)
|
|
504
|
+
expect(response.body.data).to.have.property('metas')
|
|
505
|
+
expect(response.body.data.metas.uiPreferences).to.have.property('newSetting')
|
|
506
|
+
expect(response.body.data.metas.uiPreferences.newSetting).to.eq('added-via-metas-only')
|
|
507
|
+
|
|
508
|
+
cy.log('Verified: Metas-only updates now supported')
|
|
509
|
+
})
|
|
510
|
+
})
|
|
511
|
+
})
|
|
512
|
+
|
|
513
|
+
it('TASKS_META_033: Should merge new key into existing meta group (preserving existing keys)', () => {
|
|
514
|
+
cy.then(() => {
|
|
515
|
+
// Include title to satisfy entity field requirement
|
|
516
|
+
const updateData = {
|
|
517
|
+
title: testTask.title, // No-op update to satisfy requirement
|
|
518
|
+
metas: {
|
|
519
|
+
uiPreferences: {
|
|
520
|
+
newSetting: 'added-value'
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
taskAPI.updateTask(testTask.id, updateData).then((response: any) => {
|
|
526
|
+
taskAPI.validateSuccessResponse(response, 200)
|
|
527
|
+
expect(response.body.data).to.have.property('metas')
|
|
528
|
+
expect(response.body.data.metas.uiPreferences).to.have.property('newSetting')
|
|
529
|
+
// Original keys should be preserved
|
|
530
|
+
expect(response.body.data.metas.uiPreferences).to.have.property('theme')
|
|
531
|
+
expect(response.body.data.metas.uiPreferences).to.have.property('sidebar')
|
|
532
|
+
|
|
533
|
+
cy.log('Verified: New key added, existing keys preserved')
|
|
534
|
+
})
|
|
535
|
+
})
|
|
536
|
+
})
|
|
537
|
+
|
|
538
|
+
it('TASKS_META_034: Should overwrite existing key while preserving others', () => {
|
|
539
|
+
cy.then(() => {
|
|
540
|
+
// Include title to satisfy entity field requirement
|
|
541
|
+
const updateData = {
|
|
542
|
+
title: testTask.title,
|
|
543
|
+
metas: {
|
|
544
|
+
uiPreferences: {
|
|
545
|
+
theme: 'light' // Overwrite existing
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
taskAPI.updateTask(testTask.id, updateData).then((response: any) => {
|
|
551
|
+
taskAPI.validateSuccessResponse(response, 200)
|
|
552
|
+
expect(response.body.data.metas.uiPreferences.theme).to.eq('light')
|
|
553
|
+
// Other keys should still exist
|
|
554
|
+
expect(response.body.data.metas.uiPreferences).to.have.property('sidebar')
|
|
555
|
+
|
|
556
|
+
cy.log('Verified: Key overwritten, others preserved')
|
|
557
|
+
})
|
|
558
|
+
})
|
|
559
|
+
})
|
|
560
|
+
|
|
561
|
+
it('TASKS_META_035: Should upsert (create) new metaKey if it does not exist', () => {
|
|
562
|
+
cy.then(() => {
|
|
563
|
+
// Include title to satisfy entity field requirement
|
|
564
|
+
const updateData = {
|
|
565
|
+
title: testTask.title,
|
|
566
|
+
metas: {
|
|
567
|
+
customFields: {
|
|
568
|
+
clientRef: 'NEW-001',
|
|
569
|
+
department: 'Sales'
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
taskAPI.updateTask(testTask.id, updateData).then((response: any) => {
|
|
575
|
+
taskAPI.validateSuccessResponse(response, 200)
|
|
576
|
+
expect(response.body.data.metas).to.have.property('customFields')
|
|
577
|
+
expect(response.body.data.metas.customFields.clientRef).to.eq('NEW-001')
|
|
578
|
+
|
|
579
|
+
cy.log('Verified: New metaKey created via upsert')
|
|
580
|
+
})
|
|
581
|
+
})
|
|
582
|
+
})
|
|
583
|
+
|
|
584
|
+
it('TASKS_META_036: Should update multiple meta groups simultaneously', () => {
|
|
585
|
+
cy.then(() => {
|
|
586
|
+
// Include title to satisfy entity field requirement
|
|
587
|
+
const updateData = {
|
|
588
|
+
title: testTask.title,
|
|
589
|
+
metas: {
|
|
590
|
+
uiPreferences: {
|
|
591
|
+
theme: 'system'
|
|
592
|
+
},
|
|
593
|
+
notifications: {
|
|
594
|
+
slack: true
|
|
595
|
+
},
|
|
596
|
+
tracking: {
|
|
597
|
+
actualHours: 5
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
taskAPI.updateTask(testTask.id, updateData).then((response: any) => {
|
|
603
|
+
taskAPI.validateSuccessResponse(response, 200)
|
|
604
|
+
expect(response.body.data.metas).to.have.property('uiPreferences')
|
|
605
|
+
expect(response.body.data.metas).to.have.property('notifications')
|
|
606
|
+
expect(response.body.data.metas).to.have.property('tracking')
|
|
607
|
+
|
|
608
|
+
cy.log('Verified: Multiple meta groups updated')
|
|
609
|
+
})
|
|
610
|
+
})
|
|
611
|
+
})
|
|
612
|
+
|
|
613
|
+
it('TASKS_META_037: Should handle nested object updates correctly', () => {
|
|
614
|
+
cy.then(() => {
|
|
615
|
+
// Include title to satisfy entity field requirement
|
|
616
|
+
const updateData = {
|
|
617
|
+
title: testTask.title,
|
|
618
|
+
metas: {
|
|
619
|
+
customFields: {
|
|
620
|
+
nested: {
|
|
621
|
+
level1: {
|
|
622
|
+
level2: {
|
|
623
|
+
value: 'deep-update'
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
taskAPI.updateTask(testTask.id, updateData).then((response: any) => {
|
|
632
|
+
taskAPI.validateSuccessResponse(response, 200)
|
|
633
|
+
expect(response.body.data.metas.customFields.nested.level1.level2.value).to.eq('deep-update')
|
|
634
|
+
|
|
635
|
+
cy.log('Verified: Nested objects updated correctly')
|
|
636
|
+
})
|
|
637
|
+
})
|
|
638
|
+
})
|
|
639
|
+
|
|
640
|
+
it('TASKS_META_038: Should reject update with empty metas and no task fields', () => {
|
|
641
|
+
cy.then(() => {
|
|
642
|
+
const updateData = {
|
|
643
|
+
metas: {}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
taskAPI.updateTask(testTask.id, updateData).then((response: any) => {
|
|
647
|
+
expect(response.status).to.eq(400)
|
|
648
|
+
expect(response.body.success).to.be.false
|
|
649
|
+
|
|
650
|
+
cy.log('Verified: Empty metas with no fields rejected')
|
|
651
|
+
})
|
|
652
|
+
})
|
|
653
|
+
})
|
|
654
|
+
|
|
655
|
+
it('TASKS_META_039: Should reject update with invalid metas format (string)', () => {
|
|
656
|
+
cy.then(() => {
|
|
657
|
+
const updateData = {
|
|
658
|
+
metas: 'invalid-string'
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
taskAPI.updateTask(testTask.id, updateData).then((response: any) => {
|
|
662
|
+
expect(response.status).to.eq(400)
|
|
663
|
+
expect(response.body.success).to.be.false
|
|
664
|
+
|
|
665
|
+
cy.log('Verified: Invalid metas format rejected')
|
|
666
|
+
})
|
|
667
|
+
})
|
|
668
|
+
})
|
|
669
|
+
})
|
|
670
|
+
|
|
671
|
+
// ============================================================
|
|
672
|
+
// DELETE /api/v1/tasks/{id} - Delete with Metadata
|
|
673
|
+
// ============================================================
|
|
674
|
+
describe('DELETE /api/v1/tasks/{id} - Delete with Metadata', () => {
|
|
675
|
+
it('TASKS_META_050: Should delete task with metadata successfully', () => {
|
|
676
|
+
// Create task with metas
|
|
677
|
+
const taskData = taskAPI.generateRandomTaskData({
|
|
678
|
+
title: 'Task to Delete with Metas',
|
|
679
|
+
metas: {
|
|
680
|
+
uiPreferences: { colorLabel: 'red' },
|
|
681
|
+
tracking: { actualHours: 10 }
|
|
682
|
+
}
|
|
683
|
+
})
|
|
684
|
+
|
|
685
|
+
taskAPI.createTask(taskData).then((createResponse: any) => {
|
|
686
|
+
const taskId = createResponse.body.data.id
|
|
687
|
+
|
|
688
|
+
// Verify metas exist
|
|
689
|
+
taskAPI.getTaskById(taskId, { metas: 'all' }).then((getResponse: any) => {
|
|
690
|
+
expect(getResponse.body.data.metas).to.have.property('uiPreferences')
|
|
691
|
+
|
|
692
|
+
// Now delete
|
|
693
|
+
taskAPI.deleteTask(taskId).then((deleteResponse: any) => {
|
|
694
|
+
taskAPI.validateSuccessResponse(deleteResponse, 200)
|
|
695
|
+
expect(deleteResponse.body.data.success).to.be.true
|
|
696
|
+
|
|
697
|
+
cy.log('Verified: Task with metas deleted successfully')
|
|
698
|
+
})
|
|
699
|
+
})
|
|
700
|
+
})
|
|
701
|
+
})
|
|
702
|
+
|
|
703
|
+
it('TASKS_META_051: Should verify cascade delete removes metadata', () => {
|
|
704
|
+
// Create task with metas
|
|
705
|
+
const taskData = taskAPI.generateRandomTaskData({
|
|
706
|
+
title: 'Task for Cascade Delete Test',
|
|
707
|
+
metas: {
|
|
708
|
+
customFields: { important: true }
|
|
709
|
+
}
|
|
710
|
+
})
|
|
711
|
+
|
|
712
|
+
taskAPI.createTask(taskData).then((createResponse: any) => {
|
|
713
|
+
const taskId = createResponse.body.data.id
|
|
714
|
+
|
|
715
|
+
// Delete the task
|
|
716
|
+
taskAPI.deleteTask(taskId).then((deleteResponse: any) => {
|
|
717
|
+
expect(deleteResponse.status).to.eq(200)
|
|
718
|
+
|
|
719
|
+
// Verify task is gone
|
|
720
|
+
taskAPI.getTaskById(taskId, { metas: 'all' }).then((getResponse: any) => {
|
|
721
|
+
expect(getResponse.status).to.eq(404)
|
|
722
|
+
|
|
723
|
+
cy.log('Verified: Task and metas cascade deleted')
|
|
724
|
+
})
|
|
725
|
+
})
|
|
726
|
+
})
|
|
727
|
+
})
|
|
728
|
+
|
|
729
|
+
it('TASKS_META_052: Should delete task without metadata normally', () => {
|
|
730
|
+
// Create task without metas
|
|
731
|
+
const taskData = taskAPI.generateRandomTaskData({
|
|
732
|
+
title: 'Task without Metas to Delete'
|
|
733
|
+
})
|
|
734
|
+
|
|
735
|
+
taskAPI.createTask(taskData).then((createResponse: any) => {
|
|
736
|
+
const taskId = createResponse.body.data.id
|
|
737
|
+
|
|
738
|
+
// Delete the task
|
|
739
|
+
taskAPI.deleteTask(taskId).then((deleteResponse: any) => {
|
|
740
|
+
taskAPI.validateSuccessResponse(deleteResponse, 200)
|
|
741
|
+
expect(deleteResponse.body.data.success).to.be.true
|
|
742
|
+
|
|
743
|
+
// Verify deletion
|
|
744
|
+
taskAPI.getTaskById(taskId).then((getResponse: any) => {
|
|
745
|
+
expect(getResponse.status).to.eq(404)
|
|
746
|
+
|
|
747
|
+
cy.log('Verified: Task without metas deleted normally')
|
|
748
|
+
})
|
|
749
|
+
})
|
|
750
|
+
})
|
|
751
|
+
})
|
|
752
|
+
})
|
|
753
|
+
|
|
754
|
+
// ============================================================
|
|
755
|
+
// Integration - Complete CRUD Lifecycle with Metadata
|
|
756
|
+
// ============================================================
|
|
757
|
+
describe('Integration - Complete CRUD Lifecycle with Metadata', () => {
|
|
758
|
+
it('TASKS_META_100: Should complete full lifecycle with metas: Create → Read → Update → Delete', () => {
|
|
759
|
+
const initialMetas = {
|
|
760
|
+
uiPreferences: {
|
|
761
|
+
colorLabel: 'blue',
|
|
762
|
+
priority: 'high'
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
const taskData = taskAPI.generateRandomTaskData({
|
|
767
|
+
title: 'Lifecycle Task with Metas',
|
|
768
|
+
metas: initialMetas
|
|
769
|
+
})
|
|
770
|
+
|
|
771
|
+
// 1. CREATE with metas
|
|
772
|
+
taskAPI.createTask(taskData).then((createResponse: any) => {
|
|
773
|
+
taskAPI.validateSuccessResponse(createResponse, 201)
|
|
774
|
+
expect(createResponse.body.data).to.have.property('metas')
|
|
775
|
+
expect(createResponse.body.data.metas.uiPreferences.colorLabel).to.eq('blue')
|
|
776
|
+
const taskId = createResponse.body.data.id
|
|
777
|
+
cy.log(`1. Created task with metas: ${taskId}`)
|
|
778
|
+
|
|
779
|
+
// 2. READ with metas=all
|
|
780
|
+
taskAPI.getTaskById(taskId, { metas: 'all' }).then((readResponse: any) => {
|
|
781
|
+
taskAPI.validateSuccessResponse(readResponse, 200)
|
|
782
|
+
expect(readResponse.body.data.metas).to.have.property('uiPreferences')
|
|
783
|
+
cy.log('2. Read task with all metas')
|
|
784
|
+
|
|
785
|
+
// 3. UPDATE metas (include title to satisfy entity field requirement)
|
|
786
|
+
const updateMetas = {
|
|
787
|
+
title: 'Lifecycle Task with Metas', // Required entity field
|
|
788
|
+
metas: {
|
|
789
|
+
uiPreferences: {
|
|
790
|
+
colorLabel: 'green', // Update existing
|
|
791
|
+
newField: 'added' // Add new
|
|
792
|
+
},
|
|
793
|
+
tracking: { // Add new group
|
|
794
|
+
startedAt: new Date().toISOString()
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
taskAPI.updateTask(taskId, updateMetas).then((updateResponse: any) => {
|
|
800
|
+
taskAPI.validateSuccessResponse(updateResponse, 200)
|
|
801
|
+
expect(updateResponse.body.data.metas.uiPreferences.colorLabel).to.eq('green')
|
|
802
|
+
expect(updateResponse.body.data.metas.uiPreferences.newField).to.eq('added')
|
|
803
|
+
expect(updateResponse.body.data.metas).to.have.property('tracking')
|
|
804
|
+
cy.log('3. Updated metas with merge behavior')
|
|
805
|
+
|
|
806
|
+
// 4. DELETE
|
|
807
|
+
taskAPI.deleteTask(taskId).then((deleteResponse: any) => {
|
|
808
|
+
taskAPI.validateSuccessResponse(deleteResponse, 200)
|
|
809
|
+
cy.log('4. Deleted task with metas')
|
|
810
|
+
|
|
811
|
+
// 5. VERIFY DELETION
|
|
812
|
+
taskAPI.getTaskById(taskId, { metas: 'all' }).then((finalResponse: any) => {
|
|
813
|
+
expect(finalResponse.status).to.eq(404)
|
|
814
|
+
cy.log('5. Verified deletion (404)')
|
|
815
|
+
cy.log('Full lifecycle with metas completed successfully')
|
|
816
|
+
})
|
|
817
|
+
})
|
|
818
|
+
})
|
|
819
|
+
})
|
|
820
|
+
})
|
|
821
|
+
})
|
|
822
|
+
|
|
823
|
+
it('TASKS_META_101: Should verify accumulative merge works across multiple updates', () => {
|
|
824
|
+
const taskTitle = 'Accumulative Merge Test'
|
|
825
|
+
const taskData = taskAPI.generateRandomTaskData({
|
|
826
|
+
title: taskTitle
|
|
827
|
+
})
|
|
828
|
+
|
|
829
|
+
// Create task without metas
|
|
830
|
+
taskAPI.createTask(taskData).then((createResponse: any) => {
|
|
831
|
+
const taskId = createResponse.body.data.id
|
|
832
|
+
createdTasks.push(createResponse.body.data)
|
|
833
|
+
|
|
834
|
+
// Update 1: Add first meta group (include title to satisfy requirement)
|
|
835
|
+
taskAPI.updateTask(taskId, {
|
|
836
|
+
title: taskTitle,
|
|
837
|
+
metas: {
|
|
838
|
+
uiPreferences: { theme: 'dark' }
|
|
839
|
+
}
|
|
840
|
+
}).then((update1: any) => {
|
|
841
|
+
expect(update1.body.data.metas.uiPreferences.theme).to.eq('dark')
|
|
842
|
+
|
|
843
|
+
// Update 2: Add second meta group
|
|
844
|
+
taskAPI.updateTask(taskId, {
|
|
845
|
+
title: taskTitle,
|
|
846
|
+
metas: {
|
|
847
|
+
notifications: { email: true }
|
|
848
|
+
}
|
|
849
|
+
}).then((update2: any) => {
|
|
850
|
+
expect(update2.body.data.metas).to.have.property('notifications')
|
|
851
|
+
|
|
852
|
+
// Update 3: Add key to first group
|
|
853
|
+
taskAPI.updateTask(taskId, {
|
|
854
|
+
title: taskTitle,
|
|
855
|
+
metas: {
|
|
856
|
+
uiPreferences: { sidebar: true }
|
|
857
|
+
}
|
|
858
|
+
}).then((update3: any) => {
|
|
859
|
+
// Verify all accumulated metas
|
|
860
|
+
taskAPI.getTaskById(taskId, { metas: 'all' }).then((finalGet: any) => {
|
|
861
|
+
const metas = finalGet.body.data.metas
|
|
862
|
+
|
|
863
|
+
// First group should have both keys
|
|
864
|
+
expect(metas.uiPreferences).to.have.property('theme')
|
|
865
|
+
expect(metas.uiPreferences).to.have.property('sidebar')
|
|
866
|
+
|
|
867
|
+
// Second group should still exist
|
|
868
|
+
expect(metas).to.have.property('notifications')
|
|
869
|
+
|
|
870
|
+
cy.log('Verified: Accumulative merge preserved all metas across updates')
|
|
871
|
+
})
|
|
872
|
+
})
|
|
873
|
+
})
|
|
874
|
+
})
|
|
875
|
+
})
|
|
876
|
+
})
|
|
877
|
+
})
|
|
878
|
+
})
|