@nextsparkjs/theme-default 0.1.0-beta.44 → 0.1.0-beta.45
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/components/ai-chat/ChatPanel.tsx +7 -7
- package/components/ai-chat/Message.tsx +2 -2
- package/components/ai-chat/MessageInput.tsx +3 -3
- package/components/ai-chat/MessageList.tsx +3 -3
- package/components/ai-chat/TypingIndicator.tsx +2 -2
- package/entities/customers/api/docs.md +107 -0
- package/entities/customers/api/presets.ts +80 -0
- package/entities/pages/api/docs.md +114 -0
- package/entities/pages/api/presets.ts +72 -0
- package/entities/posts/api/docs.md +120 -0
- package/entities/posts/api/presets.ts +74 -0
- package/entities/tasks/api/docs.md +126 -0
- package/entities/tasks/api/presets.ts +84 -0
- package/lib/selectors.ts +2 -2
- package/messages/de/admin.json +45 -0
- package/messages/en/admin.json +45 -0
- package/messages/es/admin.json +45 -0
- package/messages/fr/admin.json +45 -0
- package/messages/it/admin.json +45 -0
- package/messages/pt/admin.json +45 -0
- package/package.json +3 -3
- package/styles/globals.css +24 -0
- package/tests/cypress/e2e/_utils/selectors/block-editor.bdd.md +491 -0
- package/tests/cypress/e2e/_utils/selectors/block-editor.cy.ts +475 -0
- package/tests/cypress/e2e/_utils/selectors/dashboard-container.cy.ts +52 -0
- package/tests/cypress/e2e/_utils/selectors/dashboard-mobile.cy.ts +14 -14
- package/tests/cypress/e2e/_utils/selectors/dashboard-navigation.cy.ts +3 -3
- package/tests/cypress/e2e/_utils/selectors/dashboard-sidebar.bdd.md +38 -73
- package/tests/cypress/e2e/_utils/selectors/dashboard-sidebar.cy.ts +21 -42
- package/tests/cypress/e2e/_utils/selectors/dashboard-topnav.bdd.md +117 -38
- package/tests/cypress/e2e/_utils/selectors/dashboard-topnav.cy.ts +35 -12
- package/tests/cypress/e2e/_utils/selectors/settings-layout.bdd.md +50 -59
- package/tests/cypress/e2e/_utils/selectors/settings-layout.cy.ts +15 -23
- package/tests/cypress/e2e/_utils/selectors/tasks.bdd.md +395 -155
- package/tests/cypress/e2e/_utils/selectors/tasks.cy.ts +795 -174
- package/tests/cypress/e2e/api/_core/teams/teams-security.cy.ts +415 -0
- package/tests/cypress/e2e/uat/_core/teams/inline-edit.cy.ts +278 -0
- package/tests/cypress/src/core/BlockEditorBasePOM.ts +269 -99
- package/tests/cypress/src/core/DashboardEntityPOM.ts +1 -1
- package/tests/cypress/src/features/DashboardPOM.ts +49 -28
- package/tests/cypress/src/features/PageBuilderPOM.ts +20 -0
- package/tests/cypress/src/features/SettingsPOM.ts +511 -166
- package/tests/cypress/src/features/SuperadminPOM.ts +679 -159
- package/tests/cypress/src/features/index.ts +10 -10
- package/tests/cypress/e2e/_utils/selectors/pages-editor.bdd.md +0 -207
- package/tests/cypress/e2e/_utils/selectors/pages-editor.cy.ts +0 -211
- package/tests/cypress/e2e/_utils/selectors/posts-editor.bdd.md +0 -184
- package/tests/cypress/e2e/_utils/selectors/posts-editor.cy.ts +0 -350
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Teams API - Security Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for security vulnerabilities in Teams PATCH endpoint:
|
|
5
|
+
* - Issue #1: Type coercion vulnerability (empty strings, 0, false, null)
|
|
6
|
+
* - Issue #2: Permission check order (owner-only fields)
|
|
7
|
+
* - Issue #3: Schema validation (owner vs non-owner schemas)
|
|
8
|
+
*
|
|
9
|
+
* Uses existing sample data users:
|
|
10
|
+
* - Owner: Carlos Mendoza (team-everpoint-001)
|
|
11
|
+
* - Admin: James Wilson (team-everpoint-001)
|
|
12
|
+
* - Member: Emily Johnson (team-everpoint-001)
|
|
13
|
+
*
|
|
14
|
+
* @tags @api @teams @security
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import * as allure from 'allure-cypress'
|
|
18
|
+
import { loginAsOwner, loginAsAdmin, loginAsMember, BILLING_TEAMS } from '../../../../src/session-helpers'
|
|
19
|
+
|
|
20
|
+
describe('Teams API - Security Tests', { tags: ['@api', '@teams', '@security'] }, () => {
|
|
21
|
+
// Use Everpoint Labs team from sample data (Carlos is owner, James is admin, Emily is member)
|
|
22
|
+
const TEST_TEAM_ID = BILLING_TEAMS.PRO.teamId // team-everpoint-001
|
|
23
|
+
const BASE_URL = Cypress.config('baseUrl') || 'http://localhost:3000'
|
|
24
|
+
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
allure.epic('API')
|
|
27
|
+
allure.feature('Teams')
|
|
28
|
+
allure.story('Security & Permission Enforcement')
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
describe('Issue #1: Type Coercion Vulnerability', () => {
|
|
32
|
+
context('As Admin (non-owner)', () => {
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
loginAsAdmin()
|
|
35
|
+
cy.visit('/dashboard') // Required for session cookies
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('SEC_TEAMS_001: Should reject PATCH with empty string for name', { tags: '@smoke' }, () => {
|
|
39
|
+
allure.severity('critical')
|
|
40
|
+
cy.request({
|
|
41
|
+
method: 'PATCH',
|
|
42
|
+
url: `/api/v1/teams/${TEST_TEAM_ID}`,
|
|
43
|
+
body: { name: '' },
|
|
44
|
+
failOnStatusCode: false,
|
|
45
|
+
}).then((response) => {
|
|
46
|
+
expect(response.status).to.eq(403)
|
|
47
|
+
expect(response.body.success).to.be.false
|
|
48
|
+
expect(response.body.error).to.include('owner')
|
|
49
|
+
expect(response.body.reason).to.eq('OWNER_ONLY')
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('SEC_TEAMS_002: Should reject PATCH with null for description', () => {
|
|
54
|
+
cy.request({
|
|
55
|
+
method: 'PATCH',
|
|
56
|
+
url: `/api/v1/teams/${TEST_TEAM_ID}`,
|
|
57
|
+
body: { description: null },
|
|
58
|
+
failOnStatusCode: false,
|
|
59
|
+
}).then((response) => {
|
|
60
|
+
expect(response.status).to.eq(403)
|
|
61
|
+
expect(response.body.success).to.be.false
|
|
62
|
+
expect(response.body.error).to.include('owner')
|
|
63
|
+
expect(response.body.reason).to.eq('OWNER_ONLY')
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('SEC_TEAMS_003: Should reject PATCH with number (0) for name', () => {
|
|
68
|
+
cy.request({
|
|
69
|
+
method: 'PATCH',
|
|
70
|
+
url: `/api/v1/teams/${TEST_TEAM_ID}`,
|
|
71
|
+
body: { name: 0 },
|
|
72
|
+
failOnStatusCode: false,
|
|
73
|
+
}).then((response) => {
|
|
74
|
+
expect(response.status).to.eq(403)
|
|
75
|
+
expect(response.body.success).to.be.false
|
|
76
|
+
expect(response.body.error).to.include('owner')
|
|
77
|
+
expect(response.body.reason).to.eq('OWNER_ONLY')
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('SEC_TEAMS_004: Should reject PATCH with boolean (false) for description', () => {
|
|
82
|
+
cy.request({
|
|
83
|
+
method: 'PATCH',
|
|
84
|
+
url: `/api/v1/teams/${TEST_TEAM_ID}`,
|
|
85
|
+
body: { description: false },
|
|
86
|
+
failOnStatusCode: false,
|
|
87
|
+
}).then((response) => {
|
|
88
|
+
expect(response.status).to.eq(403)
|
|
89
|
+
expect(response.body.success).to.be.false
|
|
90
|
+
expect(response.body.error).to.include('owner')
|
|
91
|
+
expect(response.body.reason).to.eq('OWNER_ONLY')
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('SEC_TEAMS_005: Should reject PATCH with whitespace-only name', () => {
|
|
96
|
+
cy.request({
|
|
97
|
+
method: 'PATCH',
|
|
98
|
+
url: `/api/v1/teams/${TEST_TEAM_ID}`,
|
|
99
|
+
body: { name: ' ' },
|
|
100
|
+
failOnStatusCode: false,
|
|
101
|
+
}).then((response) => {
|
|
102
|
+
expect(response.status).to.eq(403)
|
|
103
|
+
expect(response.body.success).to.be.false
|
|
104
|
+
expect(response.body.error).to.include('owner')
|
|
105
|
+
expect(response.body.reason).to.eq('OWNER_ONLY')
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
context('As Member (non-owner)', () => {
|
|
111
|
+
beforeEach(() => {
|
|
112
|
+
loginAsMember()
|
|
113
|
+
cy.visit('/dashboard') // Required for session cookies
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('SEC_TEAMS_006: Should reject PATCH with empty string for name', () => {
|
|
117
|
+
cy.request({
|
|
118
|
+
method: 'PATCH',
|
|
119
|
+
url: `/api/v1/teams/${TEST_TEAM_ID}`,
|
|
120
|
+
body: { name: '' },
|
|
121
|
+
failOnStatusCode: false,
|
|
122
|
+
}).then((response) => {
|
|
123
|
+
expect(response.status).to.eq(403)
|
|
124
|
+
expect(response.body.success).to.be.false
|
|
125
|
+
expect(response.body.error).to.include('owner')
|
|
126
|
+
expect(response.body.reason).to.eq('OWNER_ONLY')
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('SEC_TEAMS_007: Should reject PATCH with description update', () => {
|
|
131
|
+
cy.request({
|
|
132
|
+
method: 'PATCH',
|
|
133
|
+
url: `/api/v1/teams/${TEST_TEAM_ID}`,
|
|
134
|
+
body: { description: 'New description' },
|
|
135
|
+
failOnStatusCode: false,
|
|
136
|
+
}).then((response) => {
|
|
137
|
+
expect(response.status).to.eq(403)
|
|
138
|
+
expect(response.body.success).to.be.false
|
|
139
|
+
expect(response.body.error).to.include('owner')
|
|
140
|
+
expect(response.body.reason).to.eq('OWNER_ONLY')
|
|
141
|
+
})
|
|
142
|
+
})
|
|
143
|
+
})
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
describe('Issue #2: Permission Check Order', () => {
|
|
147
|
+
context('Admin user (non-owner)', () => {
|
|
148
|
+
beforeEach(() => {
|
|
149
|
+
loginAsAdmin()
|
|
150
|
+
cy.visit('/dashboard')
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it('SEC_TEAMS_010: Should return 403 with OWNER_ONLY reason when trying to update name', { tags: '@smoke' }, () => {
|
|
154
|
+
allure.severity('critical')
|
|
155
|
+
cy.request({
|
|
156
|
+
method: 'PATCH',
|
|
157
|
+
url: `/api/v1/teams/${TEST_TEAM_ID}`,
|
|
158
|
+
body: { name: 'New Name by Admin' },
|
|
159
|
+
failOnStatusCode: false,
|
|
160
|
+
}).then((response) => {
|
|
161
|
+
expect(response.status).to.eq(403)
|
|
162
|
+
expect(response.body.success).to.be.false
|
|
163
|
+
expect(response.body.reason).to.eq('OWNER_ONLY')
|
|
164
|
+
expect(response.body.error).to.include('Only team owner')
|
|
165
|
+
expect(response.body.error).to.not.include('generic')
|
|
166
|
+
})
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
it('SEC_TEAMS_011: Should return 403 with OWNER_ONLY reason when trying to update description', () => {
|
|
170
|
+
cy.request({
|
|
171
|
+
method: 'PATCH',
|
|
172
|
+
url: `/api/v1/teams/${TEST_TEAM_ID}`,
|
|
173
|
+
body: { description: 'New Description by Admin' },
|
|
174
|
+
failOnStatusCode: false,
|
|
175
|
+
}).then((response) => {
|
|
176
|
+
expect(response.status).to.eq(403)
|
|
177
|
+
expect(response.body.success).to.be.false
|
|
178
|
+
expect(response.body.reason).to.eq('OWNER_ONLY')
|
|
179
|
+
expect(response.body.error).to.include('Only team owner')
|
|
180
|
+
})
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
it('SEC_TEAMS_012: Should return specific error, not generic permission error', () => {
|
|
184
|
+
cy.request({
|
|
185
|
+
method: 'PATCH',
|
|
186
|
+
url: `/api/v1/teams/${TEST_TEAM_ID}`,
|
|
187
|
+
body: { name: 'Test' },
|
|
188
|
+
failOnStatusCode: false,
|
|
189
|
+
}).then((response) => {
|
|
190
|
+
expect(response.status).to.eq(403)
|
|
191
|
+
expect(response.body.reason).to.eq('OWNER_ONLY')
|
|
192
|
+
// Should NOT be generic "teams.update" permission error
|
|
193
|
+
expect(response.body.reason).to.not.eq('INSUFFICIENT_PERMISSIONS')
|
|
194
|
+
})
|
|
195
|
+
})
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
context('Member user (non-owner)', () => {
|
|
199
|
+
beforeEach(() => {
|
|
200
|
+
loginAsMember()
|
|
201
|
+
cy.visit('/dashboard')
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
it('SEC_TEAMS_013: Should return 403 with OWNER_ONLY reason when trying to update name', () => {
|
|
205
|
+
cy.request({
|
|
206
|
+
method: 'PATCH',
|
|
207
|
+
url: `/api/v1/teams/${TEST_TEAM_ID}`,
|
|
208
|
+
body: { name: 'New Name by Member' },
|
|
209
|
+
failOnStatusCode: false,
|
|
210
|
+
}).then((response) => {
|
|
211
|
+
expect(response.status).to.eq(403)
|
|
212
|
+
expect(response.body.success).to.be.false
|
|
213
|
+
expect(response.body.reason).to.eq('OWNER_ONLY')
|
|
214
|
+
})
|
|
215
|
+
})
|
|
216
|
+
})
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
describe('Issue #3: Schema Validation (Owner vs Non-Owner)', () => {
|
|
220
|
+
context('Owner updates', () => {
|
|
221
|
+
beforeEach(() => {
|
|
222
|
+
loginAsOwner()
|
|
223
|
+
cy.visit('/dashboard')
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
it('SEC_TEAMS_020: Should use ownerUpdateTeamSchema for name updates', () => {
|
|
227
|
+
cy.request({
|
|
228
|
+
method: 'PATCH',
|
|
229
|
+
url: `/api/v1/teams/${TEST_TEAM_ID}`,
|
|
230
|
+
body: { name: 'A' }, // Too short (min 2 chars)
|
|
231
|
+
failOnStatusCode: false,
|
|
232
|
+
}).then((response) => {
|
|
233
|
+
expect(response.status).to.eq(400)
|
|
234
|
+
expect(response.body.success).to.be.false
|
|
235
|
+
expect(response.body.code).to.eq('VALIDATION_ERROR')
|
|
236
|
+
expect(response.body.error).to.include('at least 2 characters')
|
|
237
|
+
})
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
it('SEC_TEAMS_021: Should use ownerUpdateTeamSchema for description updates', () => {
|
|
241
|
+
cy.request({
|
|
242
|
+
method: 'PATCH',
|
|
243
|
+
url: `/api/v1/teams/${TEST_TEAM_ID}`,
|
|
244
|
+
body: { description: null },
|
|
245
|
+
failOnStatusCode: false,
|
|
246
|
+
}).then((response) => {
|
|
247
|
+
// Should accept null for description
|
|
248
|
+
expect(response.status).to.eq(200)
|
|
249
|
+
expect(response.body.success).to.be.true
|
|
250
|
+
})
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
it('SEC_TEAMS_022: Should allow valid owner updates', () => {
|
|
254
|
+
cy.request({
|
|
255
|
+
method: 'PATCH',
|
|
256
|
+
url: `/api/v1/teams/${TEST_TEAM_ID}`,
|
|
257
|
+
body: {
|
|
258
|
+
name: 'Everpoint Labs Updated',
|
|
259
|
+
description: 'Updated description',
|
|
260
|
+
},
|
|
261
|
+
}).then((response) => {
|
|
262
|
+
expect(response.status).to.eq(200)
|
|
263
|
+
expect(response.body.success).to.be.true
|
|
264
|
+
expect(response.body.data.name).to.eq('Everpoint Labs Updated')
|
|
265
|
+
expect(response.body.data.description).to.eq('Updated description')
|
|
266
|
+
})
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
// Restore original name
|
|
270
|
+
after(() => {
|
|
271
|
+
cy.request({
|
|
272
|
+
method: 'PATCH',
|
|
273
|
+
url: `/api/v1/teams/${TEST_TEAM_ID}`,
|
|
274
|
+
body: { name: 'Everpoint Labs' },
|
|
275
|
+
})
|
|
276
|
+
})
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
context('Admin updates (non-owner fields only)', () => {
|
|
280
|
+
beforeEach(() => {
|
|
281
|
+
loginAsAdmin()
|
|
282
|
+
cy.visit('/dashboard')
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
it('SEC_TEAMS_023: Should reject name/description fields before schema validation', () => {
|
|
286
|
+
// Should fail with 403 OWNER_ONLY, not 400 VALIDATION_ERROR
|
|
287
|
+
cy.request({
|
|
288
|
+
method: 'PATCH',
|
|
289
|
+
url: `/api/v1/teams/${TEST_TEAM_ID}`,
|
|
290
|
+
body: { name: 'Test' },
|
|
291
|
+
failOnStatusCode: false,
|
|
292
|
+
}).then((response) => {
|
|
293
|
+
expect(response.status).to.eq(403)
|
|
294
|
+
expect(response.body.reason).to.eq('OWNER_ONLY')
|
|
295
|
+
// Should NOT reach schema validation
|
|
296
|
+
expect(response.body.code).to.not.eq('VALIDATION_ERROR')
|
|
297
|
+
})
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
it('SEC_TEAMS_024: Should allow slug updates by admin', () => {
|
|
301
|
+
cy.request({
|
|
302
|
+
method: 'PATCH',
|
|
303
|
+
url: `/api/v1/teams/${TEST_TEAM_ID}`,
|
|
304
|
+
body: { slug: 'everpoint-labs-new' },
|
|
305
|
+
failOnStatusCode: false,
|
|
306
|
+
}).then((response) => {
|
|
307
|
+
// Current implementation: Admins can update slug
|
|
308
|
+
// If this fails, it means the permission system is correctly restricting admin slug updates
|
|
309
|
+
// We document both expected behaviors here
|
|
310
|
+
if (response.status === 200) {
|
|
311
|
+
expect(response.body.success).to.be.true
|
|
312
|
+
cy.log('✅ Admins CAN update slug (current behavior)')
|
|
313
|
+
} else if (response.status === 403) {
|
|
314
|
+
expect(response.body.reason).to.eq('OWNER_ONLY')
|
|
315
|
+
cy.log('✅ Admins CANNOT update slug (stricter behavior)')
|
|
316
|
+
}
|
|
317
|
+
})
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
it('SEC_TEAMS_025: Should allow avatarUrl updates by admin', () => {
|
|
321
|
+
cy.request({
|
|
322
|
+
method: 'PATCH',
|
|
323
|
+
url: `/api/v1/teams/${TEST_TEAM_ID}`,
|
|
324
|
+
body: { avatarUrl: 'https://example.com/avatar.png' },
|
|
325
|
+
failOnStatusCode: false,
|
|
326
|
+
}).then((response) => {
|
|
327
|
+
// Similar to slug, this documents expected behavior
|
|
328
|
+
if (response.status === 200) {
|
|
329
|
+
expect(response.body.success).to.be.true
|
|
330
|
+
cy.log('✅ Admins CAN update avatarUrl (current behavior)')
|
|
331
|
+
} else if (response.status === 403) {
|
|
332
|
+
expect(response.body.reason).to.eq('OWNER_ONLY')
|
|
333
|
+
cy.log('✅ Admins CANNOT update avatarUrl (stricter behavior)')
|
|
334
|
+
}
|
|
335
|
+
})
|
|
336
|
+
})
|
|
337
|
+
})
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
describe('Issue #10: Description Max Length Documentation', () => {
|
|
341
|
+
context('Owner updates', () => {
|
|
342
|
+
beforeEach(() => {
|
|
343
|
+
loginAsOwner()
|
|
344
|
+
cy.visit('/dashboard')
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
it('SEC_TEAMS_030: Should accept very long descriptions (TEXT type, no limit)', () => {
|
|
348
|
+
const longDescription = 'A'.repeat(10000) // 10KB text
|
|
349
|
+
|
|
350
|
+
cy.request({
|
|
351
|
+
method: 'PATCH',
|
|
352
|
+
url: `/api/v1/teams/${TEST_TEAM_ID}`,
|
|
353
|
+
body: { description: longDescription },
|
|
354
|
+
}).then((response) => {
|
|
355
|
+
expect(response.status).to.eq(200)
|
|
356
|
+
expect(response.body.success).to.be.true
|
|
357
|
+
expect(response.body.data.description).to.eq(longDescription)
|
|
358
|
+
})
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
it('SEC_TEAMS_031: Should accept null for description', () => {
|
|
362
|
+
cy.request({
|
|
363
|
+
method: 'PATCH',
|
|
364
|
+
url: `/api/v1/teams/${TEST_TEAM_ID}`,
|
|
365
|
+
body: { description: null },
|
|
366
|
+
}).then((response) => {
|
|
367
|
+
expect(response.status).to.eq(200)
|
|
368
|
+
expect(response.body.success).to.be.true
|
|
369
|
+
expect(response.body.data.description).to.be.null
|
|
370
|
+
})
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
// Restore original description
|
|
374
|
+
after(() => {
|
|
375
|
+
cy.request({
|
|
376
|
+
method: 'PATCH',
|
|
377
|
+
url: `/api/v1/teams/${TEST_TEAM_ID}`,
|
|
378
|
+
body: { description: 'Enabling digital innovation through scalable platforms' },
|
|
379
|
+
})
|
|
380
|
+
})
|
|
381
|
+
})
|
|
382
|
+
})
|
|
383
|
+
|
|
384
|
+
describe('Dual Authentication Support', () => {
|
|
385
|
+
it('SEC_TEAMS_040: Should accept session-based auth for owner', () => {
|
|
386
|
+
loginAsOwner()
|
|
387
|
+
cy.visit('/dashboard')
|
|
388
|
+
|
|
389
|
+
cy.request({
|
|
390
|
+
method: 'PATCH',
|
|
391
|
+
url: `/api/v1/teams/${TEST_TEAM_ID}`,
|
|
392
|
+
body: { description: 'Session Auth Test' },
|
|
393
|
+
}).then((response) => {
|
|
394
|
+
expect(response.status).to.eq(200)
|
|
395
|
+
expect(response.body.success).to.be.true
|
|
396
|
+
})
|
|
397
|
+
})
|
|
398
|
+
|
|
399
|
+
it('SEC_TEAMS_041: Should reject request without auth', () => {
|
|
400
|
+
cy.clearAllSessionStorage()
|
|
401
|
+
cy.clearAllCookies()
|
|
402
|
+
|
|
403
|
+
cy.request({
|
|
404
|
+
method: 'PATCH',
|
|
405
|
+
url: `/api/v1/teams/${TEST_TEAM_ID}`,
|
|
406
|
+
body: { name: 'No Auth Test' },
|
|
407
|
+
failOnStatusCode: false,
|
|
408
|
+
}).then((response) => {
|
|
409
|
+
expect(response.status).to.eq(401)
|
|
410
|
+
expect(response.body.success).to.be.false
|
|
411
|
+
expect(response.body.code).to.eq('AUTHENTICATION_FAILED')
|
|
412
|
+
})
|
|
413
|
+
})
|
|
414
|
+
})
|
|
415
|
+
})
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/// <reference types="cypress" />
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Inline Team Editing - E2E Tests
|
|
5
|
+
*
|
|
6
|
+
* Tests for inline editing of team name and description.
|
|
7
|
+
* Validates owner-only access and proper UI behavior.
|
|
8
|
+
*
|
|
9
|
+
* @see PR#1 for feature context
|
|
10
|
+
* @session 2026-01-11-pr1-translations-pom-fixes-v1
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import * as allure from 'allure-cypress'
|
|
14
|
+
|
|
15
|
+
import { DevKeyringPOM } from '../../../../src/components/DevKeyringPOM'
|
|
16
|
+
import { SettingsPOM } from '../../../../src/features/SettingsPOM'
|
|
17
|
+
|
|
18
|
+
// Test users
|
|
19
|
+
const USERS = {
|
|
20
|
+
OWNER: 'superadmin@cypress.com', // Team owner (superadmin)
|
|
21
|
+
MEMBER: 'developer@cypress.com', // Team member (developer - not owner of default team)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe('Inline Team Editing', {
|
|
25
|
+
tags: ['@uat', '@feat-teams', '@team-settings', '@regression']
|
|
26
|
+
}, () => {
|
|
27
|
+
const settings = SettingsPOM.create()
|
|
28
|
+
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
allure.epic('UAT')
|
|
31
|
+
allure.feature('Teams')
|
|
32
|
+
allure.story('Inline Team Editing')
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
// ============================================
|
|
36
|
+
// Owner Can Edit
|
|
37
|
+
// ============================================
|
|
38
|
+
|
|
39
|
+
describe('Owner Can Edit', () => {
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
cy.session('carlos-owner-inline-edit', () => {
|
|
42
|
+
cy.visit('/login')
|
|
43
|
+
const devKeyring = DevKeyringPOM.create()
|
|
44
|
+
devKeyring.validateVisible()
|
|
45
|
+
devKeyring.quickLoginByEmail(USERS.OWNER)
|
|
46
|
+
cy.url().should('include', '/dashboard')
|
|
47
|
+
})
|
|
48
|
+
cy.visit('/dashboard/settings/team', { timeout: 60000, failOnStatusCode: false })
|
|
49
|
+
settings.waitForTeam()
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('TEAM_EDIT_001: Owner can see edit buttons for name and description', { tags: '@smoke' }, () => {
|
|
53
|
+
allure.severity('critical')
|
|
54
|
+
allure.description('Verify that team owners can see edit icons for both team name and description fields')
|
|
55
|
+
|
|
56
|
+
settings.assertTeamNameEditable()
|
|
57
|
+
settings.assertTeamDescriptionEditable()
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('TEAM_EDIT_002: Owner can edit and save team name', () => {
|
|
61
|
+
allure.severity('critical')
|
|
62
|
+
allure.description('Verify that team owners can successfully edit and save the team name')
|
|
63
|
+
|
|
64
|
+
const newName = `Test Team ${Date.now()}`
|
|
65
|
+
|
|
66
|
+
settings.editAndSaveTeamName(newName)
|
|
67
|
+
|
|
68
|
+
// Wait for success feedback
|
|
69
|
+
cy.wait(500)
|
|
70
|
+
|
|
71
|
+
// Verify the new name is displayed
|
|
72
|
+
settings.getTeamNameText().should('contain', newName)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('TEAM_EDIT_003: Owner can edit and save team description', () => {
|
|
76
|
+
allure.severity('critical')
|
|
77
|
+
allure.description('Verify that team owners can successfully edit and save the team description')
|
|
78
|
+
|
|
79
|
+
const newDesc = `Updated description ${Date.now()}`
|
|
80
|
+
|
|
81
|
+
settings.editAndSaveTeamDescription(newDesc)
|
|
82
|
+
|
|
83
|
+
// Wait for success feedback
|
|
84
|
+
cy.wait(500)
|
|
85
|
+
|
|
86
|
+
// Verify the new description is displayed
|
|
87
|
+
settings.getTeamDescriptionText().should('contain', newDesc)
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('TEAM_EDIT_004: Cancel editing team name preserves original value', () => {
|
|
91
|
+
allure.severity('normal')
|
|
92
|
+
allure.description('Verify that canceling team name editing does not save changes')
|
|
93
|
+
|
|
94
|
+
// Get original name
|
|
95
|
+
settings.getTeamNameText().then((originalName) => {
|
|
96
|
+
// Start editing
|
|
97
|
+
settings.editTeamName()
|
|
98
|
+
settings.typeTeamName('Changed Name That Should Not Save')
|
|
99
|
+
settings.cancelTeamName()
|
|
100
|
+
|
|
101
|
+
// Verify original name is preserved
|
|
102
|
+
settings.getTeamNameText().should('equal', originalName)
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('TEAM_EDIT_005: Cancel editing team description preserves original value', () => {
|
|
107
|
+
allure.severity('normal')
|
|
108
|
+
allure.description('Verify that canceling team description editing does not save changes')
|
|
109
|
+
|
|
110
|
+
// Get original description
|
|
111
|
+
settings.getTeamDescriptionText().then((originalDesc) => {
|
|
112
|
+
// Start editing
|
|
113
|
+
settings.editTeamDescription()
|
|
114
|
+
settings.typeTeamDescription('Changed description that should not save')
|
|
115
|
+
settings.cancelTeamDescription()
|
|
116
|
+
|
|
117
|
+
// Verify original description is preserved
|
|
118
|
+
settings.getTeamDescriptionText().should('equal', originalDesc)
|
|
119
|
+
})
|
|
120
|
+
})
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
// ============================================
|
|
124
|
+
// Member Cannot Edit
|
|
125
|
+
// ============================================
|
|
126
|
+
|
|
127
|
+
describe('Member Cannot Edit', () => {
|
|
128
|
+
beforeEach(() => {
|
|
129
|
+
cy.session('emily-member-inline-edit', () => {
|
|
130
|
+
cy.visit('/login')
|
|
131
|
+
const devKeyring = DevKeyringPOM.create()
|
|
132
|
+
devKeyring.validateVisible()
|
|
133
|
+
devKeyring.quickLoginByEmail(USERS.MEMBER)
|
|
134
|
+
cy.url().should('include', '/dashboard')
|
|
135
|
+
})
|
|
136
|
+
cy.visit('/dashboard/settings/team', { timeout: 60000, failOnStatusCode: false })
|
|
137
|
+
settings.waitForTeam()
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it('TEAM_EDIT_010: Member cannot see edit button for team name', { tags: '@smoke' }, () => {
|
|
141
|
+
allure.severity('critical')
|
|
142
|
+
allure.description('Verify that non-owner team members cannot see edit icons for team name')
|
|
143
|
+
|
|
144
|
+
settings.assertTeamNameNotEditable()
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it('TEAM_EDIT_011: Member cannot see edit button for team description', { tags: '@smoke' }, () => {
|
|
148
|
+
allure.severity('critical')
|
|
149
|
+
allure.description('Verify that non-owner team members cannot see edit icons for team description')
|
|
150
|
+
|
|
151
|
+
settings.assertTeamDescriptionNotEditable()
|
|
152
|
+
})
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
// ============================================
|
|
156
|
+
// Validation Tests
|
|
157
|
+
// ============================================
|
|
158
|
+
|
|
159
|
+
describe('Validation', () => {
|
|
160
|
+
beforeEach(() => {
|
|
161
|
+
cy.session('carlos-validation-inline-edit', () => {
|
|
162
|
+
cy.visit('/login')
|
|
163
|
+
const devKeyring = DevKeyringPOM.create()
|
|
164
|
+
devKeyring.validateVisible()
|
|
165
|
+
devKeyring.quickLoginByEmail(USERS.OWNER)
|
|
166
|
+
cy.url().should('include', '/dashboard')
|
|
167
|
+
})
|
|
168
|
+
cy.visit('/dashboard/settings/team', { timeout: 60000, failOnStatusCode: false })
|
|
169
|
+
settings.waitForTeam()
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
it('TEAM_EDIT_020: Name validation - minimum length (2 characters)', () => {
|
|
173
|
+
allure.severity('normal')
|
|
174
|
+
allure.description('Verify that team name must have at least 2 characters')
|
|
175
|
+
|
|
176
|
+
// Click edit, enter single character, try to save
|
|
177
|
+
settings.editTeamName()
|
|
178
|
+
settings.typeTeamName('A')
|
|
179
|
+
settings.saveTeamName()
|
|
180
|
+
|
|
181
|
+
// Should show validation error (in any locale)
|
|
182
|
+
cy.contains(/at least 2|mindestens 2|almeno 2|pelo menos 2|au moins 2|mínimo 2/i, { timeout: 5000 })
|
|
183
|
+
.should('be.visible')
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
it('TEAM_EDIT_021: Empty team name shows validation error', () => {
|
|
187
|
+
allure.severity('normal')
|
|
188
|
+
allure.description('Verify that team name cannot be empty')
|
|
189
|
+
|
|
190
|
+
// Click edit, clear input, try to save
|
|
191
|
+
settings.editTeamName()
|
|
192
|
+
cy.get(settings.selectors.teamEditNameInput).clear()
|
|
193
|
+
settings.saveTeamName()
|
|
194
|
+
|
|
195
|
+
// Should show validation error (in any locale)
|
|
196
|
+
cy.contains(/required|erforderlich|obbligatorio|obrigatório|requis/i, { timeout: 5000 })
|
|
197
|
+
.should('be.visible')
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
it('TEAM_EDIT_022: Empty team description is allowed', () => {
|
|
201
|
+
allure.severity('normal')
|
|
202
|
+
allure.description('Verify that team description can be empty (optional field)')
|
|
203
|
+
|
|
204
|
+
// Click edit, clear textarea, save
|
|
205
|
+
settings.editTeamDescription()
|
|
206
|
+
cy.get(settings.selectors.teamEditDescriptionTextarea).clear()
|
|
207
|
+
settings.saveTeamDescription()
|
|
208
|
+
|
|
209
|
+
// Wait for save to complete
|
|
210
|
+
cy.wait(500)
|
|
211
|
+
|
|
212
|
+
// No error should appear - description is optional
|
|
213
|
+
cy.get(settings.selectors.teamEditDescriptionError).should('not.exist')
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
it('TEAM_EDIT_023: Name validation - maximum length (100 characters)', () => {
|
|
217
|
+
allure.severity('normal')
|
|
218
|
+
allure.description('Verify that team name cannot exceed 100 characters')
|
|
219
|
+
|
|
220
|
+
const longName = 'A'.repeat(101) // 101 characters
|
|
221
|
+
|
|
222
|
+
settings.editTeamName()
|
|
223
|
+
settings.typeTeamName(longName)
|
|
224
|
+
settings.saveTeamName()
|
|
225
|
+
|
|
226
|
+
// Should show validation error (in any locale)
|
|
227
|
+
cy.contains(/maximum|maximal|massimo|máximo/i, { timeout: 5000 })
|
|
228
|
+
.should('be.visible')
|
|
229
|
+
})
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
// ============================================
|
|
233
|
+
// Multi-Locale Tests
|
|
234
|
+
// ============================================
|
|
235
|
+
|
|
236
|
+
describe('Multi-Locale Support', () => {
|
|
237
|
+
const locales = [
|
|
238
|
+
{ code: 'de', name: 'German' },
|
|
239
|
+
{ code: 'fr', name: 'French' },
|
|
240
|
+
{ code: 'it', name: 'Italian' },
|
|
241
|
+
{ code: 'pt', name: 'Portuguese' },
|
|
242
|
+
]
|
|
243
|
+
|
|
244
|
+
locales.forEach(({ code, name }) => {
|
|
245
|
+
it(`TEAM_EDIT_030_${code.toUpperCase()}: Inline editing works in ${name} locale`, () => {
|
|
246
|
+
allure.severity('normal')
|
|
247
|
+
allure.description(`Verify that inline team editing displays correctly in ${name} locale`)
|
|
248
|
+
|
|
249
|
+
// Login as owner
|
|
250
|
+
cy.session(`carlos-locale-${code}`, () => {
|
|
251
|
+
cy.visit('/login')
|
|
252
|
+
const devKeyring = DevKeyringPOM.create()
|
|
253
|
+
devKeyring.validateVisible()
|
|
254
|
+
devKeyring.quickLoginByEmail(USERS.OWNER)
|
|
255
|
+
cy.url().should('include', '/dashboard')
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
// Visit with locale query param
|
|
259
|
+
cy.visit(`/dashboard/settings/team?locale=${code}`, { timeout: 60000, failOnStatusCode: false })
|
|
260
|
+
settings.waitForTeam()
|
|
261
|
+
|
|
262
|
+
// Verify edit icons are visible (selectors are locale-independent)
|
|
263
|
+
settings.assertTeamNameEditable()
|
|
264
|
+
settings.assertTeamDescriptionEditable()
|
|
265
|
+
|
|
266
|
+
// Test editing flow
|
|
267
|
+
const testName = `Test ${name} ${Date.now()}`
|
|
268
|
+
settings.editAndSaveTeamName(testName)
|
|
269
|
+
|
|
270
|
+
// Wait for save
|
|
271
|
+
cy.wait(500)
|
|
272
|
+
|
|
273
|
+
// Verify new name appears
|
|
274
|
+
settings.getTeamNameText().should('contain', testName)
|
|
275
|
+
})
|
|
276
|
+
})
|
|
277
|
+
})
|
|
278
|
+
})
|