@nextsparkjs/theme-default 0.1.0-beta.20 → 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.
- 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.config.ts +150 -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,923 @@
|
|
|
1
|
+
/// <reference types="cypress" />
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* API Keys API - CRUD Tests
|
|
5
|
+
*
|
|
6
|
+
* Comprehensive test suite for the API Keys management endpoints.
|
|
7
|
+
* Tests CRUD operations with superadmin API key authentication.
|
|
8
|
+
*
|
|
9
|
+
* @endpoint /api/v1/api-keys
|
|
10
|
+
* @see api-keys-crud.md for documentation
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import * as allure from 'allure-cypress'
|
|
14
|
+
|
|
15
|
+
const ApiKeysAPIController = require('../../../src/controllers/ApiKeysAPIController.js')
|
|
16
|
+
|
|
17
|
+
describe('API Keys API - CRUD Operations', {
|
|
18
|
+
tags: ['@api', '@feat-api-keys', '@crud', '@security', '@regression']
|
|
19
|
+
}, () => {
|
|
20
|
+
let apiKeysAPI: any
|
|
21
|
+
const testApiKeys: string[] = [] // Track created API keys for cleanup
|
|
22
|
+
|
|
23
|
+
const SUPERADMIN_API_KEY = 'test_api_key_for_testing_purposes_only_not_a_real_secret_key_abc123'
|
|
24
|
+
const BASE_URL = Cypress.config('baseUrl') || 'http://localhost:5173'
|
|
25
|
+
|
|
26
|
+
before(() => {
|
|
27
|
+
apiKeysAPI = new ApiKeysAPIController(BASE_URL, SUPERADMIN_API_KEY)
|
|
28
|
+
cy.log('Initialized ApiKeysAPIController with superadmin API key')
|
|
29
|
+
|
|
30
|
+
// Cleanup existing test API keys before running tests
|
|
31
|
+
cy.request({
|
|
32
|
+
method: 'GET',
|
|
33
|
+
url: `${BASE_URL}/api/v1/api-keys`,
|
|
34
|
+
headers: {
|
|
35
|
+
'Authorization': `Bearer ${SUPERADMIN_API_KEY}`,
|
|
36
|
+
'Content-Type': 'application/json'
|
|
37
|
+
},
|
|
38
|
+
failOnStatusCode: false
|
|
39
|
+
}).then((response: any) => {
|
|
40
|
+
if (response.status === 200 && response.body.data) {
|
|
41
|
+
const cypressKeys = response.body.data.filter((apiKey: any) =>
|
|
42
|
+
apiKey.name && apiKey.name.toLowerCase().includes('cypress')
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
cypressKeys.forEach((apiKey: any) => {
|
|
46
|
+
cy.request({
|
|
47
|
+
method: 'DELETE',
|
|
48
|
+
url: `${BASE_URL}/api/v1/api-keys/${apiKey.id}`,
|
|
49
|
+
headers: {
|
|
50
|
+
'Authorization': `Bearer ${SUPERADMIN_API_KEY}`,
|
|
51
|
+
'Content-Type': 'application/json'
|
|
52
|
+
},
|
|
53
|
+
failOnStatusCode: false
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
afterEach(() => {
|
|
61
|
+
// Cleanup API keys created during each test
|
|
62
|
+
testApiKeys.forEach((apiKeyId: string) => {
|
|
63
|
+
if (apiKeyId) {
|
|
64
|
+
cy.request({
|
|
65
|
+
method: 'DELETE',
|
|
66
|
+
url: `${BASE_URL}/api/v1/api-keys/${apiKeyId}`,
|
|
67
|
+
headers: apiKeysAPI.getHeaders(),
|
|
68
|
+
failOnStatusCode: false
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
testApiKeys.length = 0
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
beforeEach(() => {
|
|
76
|
+
allure.epic('API')
|
|
77
|
+
allure.feature('API Keys')
|
|
78
|
+
allure.story('CRUD Operations')
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
after(() => {
|
|
82
|
+
// Final cleanup - use simple request-based cleanup to avoid async issues
|
|
83
|
+
cy.request({
|
|
84
|
+
method: 'GET',
|
|
85
|
+
url: `${BASE_URL}/api/v1/api-keys`,
|
|
86
|
+
headers: {
|
|
87
|
+
'Authorization': `Bearer ${SUPERADMIN_API_KEY}`,
|
|
88
|
+
'Content-Type': 'application/json'
|
|
89
|
+
},
|
|
90
|
+
failOnStatusCode: false
|
|
91
|
+
}).then((response: any) => {
|
|
92
|
+
if (response.status === 200 && response.body.data) {
|
|
93
|
+
const cypressKeys = response.body.data.filter((apiKey: any) =>
|
|
94
|
+
apiKey.name && apiKey.name.toLowerCase().includes('cypress')
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
cypressKeys.forEach((apiKey: any) => {
|
|
98
|
+
cy.request({
|
|
99
|
+
method: 'DELETE',
|
|
100
|
+
url: `${BASE_URL}/api/v1/api-keys/${apiKey.id}`,
|
|
101
|
+
headers: {
|
|
102
|
+
'Authorization': `Bearer ${SUPERADMIN_API_KEY}`,
|
|
103
|
+
'Content-Type': 'application/json'
|
|
104
|
+
},
|
|
105
|
+
failOnStatusCode: false
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
// ============================================
|
|
113
|
+
// GET /api/v1/api-keys - List API Keys
|
|
114
|
+
// ============================================
|
|
115
|
+
|
|
116
|
+
describe('GET /api/v1/api-keys - List API Keys', () => {
|
|
117
|
+
|
|
118
|
+
it('APIKEY_001: Should list API keys with valid superadmin API key', { tags: '@smoke' }, () => {
|
|
119
|
+
allure.severity('critical')
|
|
120
|
+
apiKeysAPI.getApiKeys().then((response: any) => {
|
|
121
|
+
expect(response.status).to.eq(200)
|
|
122
|
+
expect(response.body).to.have.property('success', true)
|
|
123
|
+
expect(response.body).to.have.property('data')
|
|
124
|
+
expect(response.body.data).to.be.an('array')
|
|
125
|
+
expect(response.body).to.have.property('info')
|
|
126
|
+
expect(response.body.info).to.have.property('timestamp')
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('APIKEY_002: Should accept pagination parameters', () => {
|
|
131
|
+
apiKeysAPI.getApiKeys({ page: 1, limit: 5 }).then((response: any) => {
|
|
132
|
+
expect(response.status).to.eq(200)
|
|
133
|
+
expect(response.body).to.have.property('success', true)
|
|
134
|
+
expect(response.body.data).to.be.an('array')
|
|
135
|
+
})
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('APIKEY_003: Should reject request without API key', () => {
|
|
139
|
+
const unauthenticatedAPI = new ApiKeysAPIController(BASE_URL, null)
|
|
140
|
+
|
|
141
|
+
unauthenticatedAPI.getApiKeys().then((response: any) => {
|
|
142
|
+
expect(response.status).to.eq(401)
|
|
143
|
+
expect(response.body).to.have.property('success', false)
|
|
144
|
+
expect(response.body).to.have.property('code')
|
|
145
|
+
expect(['MISSING_API_KEY', 'AUTHENTICATION_FAILED']).to.include(response.body.code)
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it('APIKEY_004: Should reject request with invalid API key', () => {
|
|
150
|
+
const invalidAPI = new ApiKeysAPIController(BASE_URL, 'test_invalid_key_for_testing_purposes_only_not_real_12345678901234567')
|
|
151
|
+
|
|
152
|
+
invalidAPI.getApiKeys().then((response: any) => {
|
|
153
|
+
expect(response.status).to.eq(401)
|
|
154
|
+
expect(response.body).to.have.property('success', false)
|
|
155
|
+
expect(['INVALID_API_KEY', 'AUTHENTICATION_FAILED']).to.include(response.body.code)
|
|
156
|
+
})
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
it('APIKEY_005: Should reject request with malformed API key', () => {
|
|
160
|
+
const malformedAPI = new ApiKeysAPIController(BASE_URL, 'not_a_valid_key')
|
|
161
|
+
|
|
162
|
+
malformedAPI.getApiKeys().then((response: any) => {
|
|
163
|
+
expect(response.status).to.eq(401)
|
|
164
|
+
expect(response.body).to.have.property('success', false)
|
|
165
|
+
expect(['INVALID_API_KEY', 'INVALID_API_KEY_FORMAT', 'AUTHENTICATION_FAILED']).to.include(response.body.code)
|
|
166
|
+
})
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
it('APIKEY_006: Should validate response structure', () => {
|
|
170
|
+
apiKeysAPI.getApiKeys().then((response: any) => {
|
|
171
|
+
expect(response.status).to.eq(200)
|
|
172
|
+
expect(response.body).to.have.property('success', true)
|
|
173
|
+
expect(response.body).to.have.property('data')
|
|
174
|
+
expect(response.body).to.have.property('info')
|
|
175
|
+
expect(response.body.info).to.have.property('timestamp')
|
|
176
|
+
expect(response.body.info.timestamp).to.be.a('string')
|
|
177
|
+
})
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
it('APIKEY_007: Should validate API key object structure in list', () => {
|
|
181
|
+
apiKeysAPI.getApiKeys().then((response: any) => {
|
|
182
|
+
expect(response.status).to.eq(200)
|
|
183
|
+
|
|
184
|
+
if (response.body.data.length > 0) {
|
|
185
|
+
const apiKey = response.body.data[0]
|
|
186
|
+
expect(apiKey).to.have.property('id')
|
|
187
|
+
expect(apiKey).to.have.property('name')
|
|
188
|
+
expect(apiKey).to.have.property('keyPrefix')
|
|
189
|
+
expect(apiKey).to.have.property('scopes')
|
|
190
|
+
expect(apiKey).to.have.property('status')
|
|
191
|
+
expect(apiKey).to.have.property('createdAt')
|
|
192
|
+
expect(apiKey.scopes).to.be.an('array')
|
|
193
|
+
expect(apiKey.status).to.be.oneOf(['active', 'inactive', 'expired'])
|
|
194
|
+
}
|
|
195
|
+
})
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
it('APIKEY_008: Should handle large limit values', () => {
|
|
199
|
+
apiKeysAPI.getApiKeys({ limit: 100 }).then((response: any) => {
|
|
200
|
+
expect(response.status).to.eq(200)
|
|
201
|
+
expect(response.body).to.have.property('success', true)
|
|
202
|
+
expect(response.body.data).to.be.an('array')
|
|
203
|
+
})
|
|
204
|
+
})
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
// ============================================
|
|
208
|
+
// POST /api/v1/api-keys - Create API Key
|
|
209
|
+
// ============================================
|
|
210
|
+
|
|
211
|
+
describe('POST /api/v1/api-keys - Create API Key', () => {
|
|
212
|
+
|
|
213
|
+
it('APIKEY_010: Should create API key with valid data', { tags: '@smoke' }, () => {
|
|
214
|
+
allure.severity('critical')
|
|
215
|
+
const apiKeyData = {
|
|
216
|
+
name: 'Cypress Test Key APIKEY_010',
|
|
217
|
+
scopes: ['users:read', 'tasks:read'],
|
|
218
|
+
expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString()
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
apiKeysAPI.createApiKey(apiKeyData).then((response: any) => {
|
|
222
|
+
if (response.status === 429) {
|
|
223
|
+
cy.log('Rate limit reached, skipping test')
|
|
224
|
+
return
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
expect(response.status).to.eq(201)
|
|
228
|
+
expect(response.body).to.have.property('success', true)
|
|
229
|
+
expect(response.body.data).to.have.property('id')
|
|
230
|
+
expect(response.body.data).to.have.property('key')
|
|
231
|
+
expect(response.body.data).to.have.property('name', apiKeyData.name)
|
|
232
|
+
expect(response.body.data).to.have.property('scopes')
|
|
233
|
+
expect(response.body.data.scopes).to.deep.eq(apiKeyData.scopes)
|
|
234
|
+
expect(response.body.data).to.have.property('warning')
|
|
235
|
+
|
|
236
|
+
testApiKeys.push(response.body.data.id)
|
|
237
|
+
})
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
it('APIKEY_011: Should create API key with minimal data', () => {
|
|
241
|
+
const apiKeyData = {
|
|
242
|
+
name: 'Cypress Minimal Key APIKEY_011',
|
|
243
|
+
scopes: ['users:read']
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
apiKeysAPI.createApiKey(apiKeyData).then((response: any) => {
|
|
247
|
+
if (response.status === 429) {
|
|
248
|
+
cy.log('Rate limit reached, skipping test')
|
|
249
|
+
return
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
expect(response.status).to.eq(201)
|
|
253
|
+
expect(response.body).to.have.property('success', true)
|
|
254
|
+
expect(response.body.data).to.have.property('name', apiKeyData.name)
|
|
255
|
+
expect(response.body.data.scopes).to.deep.eq(apiKeyData.scopes)
|
|
256
|
+
|
|
257
|
+
testApiKeys.push(response.body.data.id)
|
|
258
|
+
})
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
it('APIKEY_012: Should create API key with expiresAt', () => {
|
|
262
|
+
const futureDate = new Date(Date.now() + 90 * 24 * 60 * 60 * 1000).toISOString()
|
|
263
|
+
const apiKeyData = {
|
|
264
|
+
name: 'Cypress Expiring Key APIKEY_012',
|
|
265
|
+
scopes: ['users:read'],
|
|
266
|
+
expiresAt: futureDate
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
apiKeysAPI.createApiKey(apiKeyData).then((response: any) => {
|
|
270
|
+
if (response.status === 429) {
|
|
271
|
+
cy.log('Rate limit reached, skipping test')
|
|
272
|
+
return
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
expect(response.status).to.eq(201)
|
|
276
|
+
expect(response.body.data).to.have.property('expiresAt')
|
|
277
|
+
|
|
278
|
+
testApiKeys.push(response.body.data.id)
|
|
279
|
+
})
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
it('APIKEY_013: Should reject creation without name', () => {
|
|
283
|
+
const apiKeyData = {
|
|
284
|
+
scopes: ['users:read']
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
apiKeysAPI.createApiKey(apiKeyData).then((response: any) => {
|
|
288
|
+
expect(response.status).to.eq(400)
|
|
289
|
+
expect(response.body).to.have.property('success', false)
|
|
290
|
+
expect(response.body).to.have.property('code', 'VALIDATION_ERROR')
|
|
291
|
+
})
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
it('APIKEY_014: Should reject creation with empty name', () => {
|
|
295
|
+
const apiKeyData = {
|
|
296
|
+
name: '',
|
|
297
|
+
scopes: ['users:read']
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
apiKeysAPI.createApiKey(apiKeyData).then((response: any) => {
|
|
301
|
+
expect(response.status).to.eq(400)
|
|
302
|
+
expect(response.body).to.have.property('success', false)
|
|
303
|
+
expect(response.body).to.have.property('code', 'VALIDATION_ERROR')
|
|
304
|
+
})
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
it('APIKEY_015: Should reject name longer than 100 characters', () => {
|
|
308
|
+
const apiKeyData = {
|
|
309
|
+
name: 'A'.repeat(101),
|
|
310
|
+
scopes: ['users:read']
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
apiKeysAPI.createApiKey(apiKeyData).then((response: any) => {
|
|
314
|
+
expect(response.status).to.eq(400)
|
|
315
|
+
expect(response.body).to.have.property('success', false)
|
|
316
|
+
expect(response.body).to.have.property('code', 'VALIDATION_ERROR')
|
|
317
|
+
})
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
it('APIKEY_016: Should reject creation without scopes', () => {
|
|
321
|
+
const apiKeyData = {
|
|
322
|
+
name: 'Cypress No Scopes Key'
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
apiKeysAPI.createApiKey(apiKeyData).then((response: any) => {
|
|
326
|
+
expect(response.status).to.eq(400)
|
|
327
|
+
expect(response.body).to.have.property('success', false)
|
|
328
|
+
expect(response.body).to.have.property('code', 'VALIDATION_ERROR')
|
|
329
|
+
})
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
it('APIKEY_017: Should reject creation with empty scopes array', () => {
|
|
333
|
+
const apiKeyData = {
|
|
334
|
+
name: 'Cypress Empty Scopes Key',
|
|
335
|
+
scopes: []
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
apiKeysAPI.createApiKey(apiKeyData).then((response: any) => {
|
|
339
|
+
expect(response.status).to.eq(400)
|
|
340
|
+
expect(response.body).to.have.property('success', false)
|
|
341
|
+
expect(response.body).to.have.property('code', 'VALIDATION_ERROR')
|
|
342
|
+
})
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
it('APIKEY_018: Should reject creation with invalid scopes', () => {
|
|
346
|
+
const apiKeyData = {
|
|
347
|
+
name: 'Cypress Invalid Scopes Key',
|
|
348
|
+
scopes: ['invalid:scope', 'another:invalid']
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
apiKeysAPI.createApiKey(apiKeyData).then((response: any) => {
|
|
352
|
+
expect(response.status).to.eq(400)
|
|
353
|
+
expect(response.body).to.have.property('success', false)
|
|
354
|
+
expect(response.body).to.have.property('code', 'INVALID_SCOPES')
|
|
355
|
+
})
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
it('APIKEY_019: Should reject creation with mixed valid/invalid scopes', () => {
|
|
359
|
+
const apiKeyData = {
|
|
360
|
+
name: 'Cypress Mixed Scopes Key',
|
|
361
|
+
scopes: ['users:read', 'invalid:scope']
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
apiKeysAPI.createApiKey(apiKeyData).then((response: any) => {
|
|
365
|
+
expect(response.status).to.eq(400)
|
|
366
|
+
expect(response.body).to.have.property('success', false)
|
|
367
|
+
expect(response.body).to.have.property('code', 'INVALID_SCOPES')
|
|
368
|
+
})
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
it('APIKEY_020: Should reject creation with invalid expiresAt format', () => {
|
|
372
|
+
const apiKeyData = {
|
|
373
|
+
name: 'Cypress Invalid Date Key',
|
|
374
|
+
scopes: ['users:read'],
|
|
375
|
+
expiresAt: 'not-a-valid-date'
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
apiKeysAPI.createApiKey(apiKeyData).then((response: any) => {
|
|
379
|
+
expect(response.status).to.eq(400)
|
|
380
|
+
expect(response.body).to.have.property('success', false)
|
|
381
|
+
expect(response.body).to.have.property('code', 'VALIDATION_ERROR')
|
|
382
|
+
})
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
it('APIKEY_021: Should reject without authorization', () => {
|
|
386
|
+
const unauthenticatedAPI = new ApiKeysAPIController(BASE_URL, null)
|
|
387
|
+
const apiKeyData = {
|
|
388
|
+
name: 'Cypress Unauth Key',
|
|
389
|
+
scopes: ['users:read']
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
unauthenticatedAPI.createApiKey(apiKeyData).then((response: any) => {
|
|
393
|
+
expect(response.status).to.eq(401)
|
|
394
|
+
expect(response.body).to.have.property('success', false)
|
|
395
|
+
})
|
|
396
|
+
})
|
|
397
|
+
})
|
|
398
|
+
|
|
399
|
+
// ============================================
|
|
400
|
+
// GET /api/v1/api-keys/{id} - Get by ID
|
|
401
|
+
// ============================================
|
|
402
|
+
|
|
403
|
+
describe('GET /api/v1/api-keys/{id} - Get API Key by ID', () => {
|
|
404
|
+
let testApiKeyId: string
|
|
405
|
+
|
|
406
|
+
beforeEach(() => {
|
|
407
|
+
// Create a test API key for GET tests
|
|
408
|
+
const apiKeyData = {
|
|
409
|
+
name: 'Cypress Test Key for GET',
|
|
410
|
+
scopes: ['users:read', 'tasks:read']
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
cy.then(() => {
|
|
414
|
+
return apiKeysAPI.createApiKey(apiKeyData)
|
|
415
|
+
}).then((response: any) => {
|
|
416
|
+
if (response.status === 201) {
|
|
417
|
+
testApiKeyId = response.body.data.id
|
|
418
|
+
testApiKeys.push(testApiKeyId)
|
|
419
|
+
} else {
|
|
420
|
+
cy.log('Could not create test API key, some tests may fail')
|
|
421
|
+
}
|
|
422
|
+
})
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
it('APIKEY_030: Should get API key by valid ID with usage stats', () => {
|
|
426
|
+
cy.then(() => testApiKeyId).then((id) => {
|
|
427
|
+
if (!id) {
|
|
428
|
+
cy.log('No test API key available, skipping')
|
|
429
|
+
return
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
apiKeysAPI.getApiKeyById(id).then((response: any) => {
|
|
433
|
+
expect(response.status).to.eq(200)
|
|
434
|
+
expect(response.body).to.have.property('success', true)
|
|
435
|
+
expect(response.body.data).to.have.property('id', id)
|
|
436
|
+
expect(response.body.data).to.have.property('name')
|
|
437
|
+
expect(response.body.data).to.have.property('usage_stats')
|
|
438
|
+
expect(response.body.data.usage_stats).to.be.an('object')
|
|
439
|
+
})
|
|
440
|
+
})
|
|
441
|
+
})
|
|
442
|
+
|
|
443
|
+
it('APIKEY_031: Should return 404 for non-existent API key ID', () => {
|
|
444
|
+
const nonExistentId = 'non-existent-id-12345'
|
|
445
|
+
|
|
446
|
+
apiKeysAPI.getApiKeyById(nonExistentId).then((response: any) => {
|
|
447
|
+
expect(response.status).to.eq(404)
|
|
448
|
+
expect(response.body).to.have.property('success', false)
|
|
449
|
+
expect(response.body).to.have.property('code', 'API_KEY_NOT_FOUND')
|
|
450
|
+
})
|
|
451
|
+
})
|
|
452
|
+
|
|
453
|
+
it('APIKEY_032: Should reject request without authorization', () => {
|
|
454
|
+
const unauthenticatedAPI = new ApiKeysAPIController(BASE_URL, null)
|
|
455
|
+
|
|
456
|
+
cy.then(() => testApiKeyId).then((id) => {
|
|
457
|
+
if (!id) {
|
|
458
|
+
cy.log('No test API key available, skipping')
|
|
459
|
+
return
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
unauthenticatedAPI.getApiKeyById(id).then((response: any) => {
|
|
463
|
+
expect(response.status).to.eq(401)
|
|
464
|
+
expect(response.body).to.have.property('success', false)
|
|
465
|
+
})
|
|
466
|
+
})
|
|
467
|
+
})
|
|
468
|
+
|
|
469
|
+
it('APIKEY_033: Should reject request with invalid API key', () => {
|
|
470
|
+
const invalidAPI = new ApiKeysAPIController(BASE_URL, 'invalid_key')
|
|
471
|
+
|
|
472
|
+
cy.then(() => testApiKeyId).then((id) => {
|
|
473
|
+
if (!id) {
|
|
474
|
+
cy.log('No test API key available, skipping')
|
|
475
|
+
return
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
invalidAPI.getApiKeyById(id).then((response: any) => {
|
|
479
|
+
expect(response.status).to.eq(401)
|
|
480
|
+
expect(response.body).to.have.property('success', false)
|
|
481
|
+
})
|
|
482
|
+
})
|
|
483
|
+
})
|
|
484
|
+
|
|
485
|
+
it('APIKEY_034: Should validate usage_stats structure', () => {
|
|
486
|
+
cy.then(() => testApiKeyId).then((id) => {
|
|
487
|
+
if (!id) {
|
|
488
|
+
cy.log('No test API key available, skipping')
|
|
489
|
+
return
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
apiKeysAPI.getApiKeyById(id).then((response: any) => {
|
|
493
|
+
expect(response.status).to.eq(200)
|
|
494
|
+
expect(response.body.data).to.have.property('usage_stats')
|
|
495
|
+
|
|
496
|
+
const stats = response.body.data.usage_stats
|
|
497
|
+
expect(stats).to.have.property('total_requests')
|
|
498
|
+
expect(stats).to.have.property('last_24h')
|
|
499
|
+
})
|
|
500
|
+
})
|
|
501
|
+
})
|
|
502
|
+
})
|
|
503
|
+
|
|
504
|
+
// ============================================
|
|
505
|
+
// PATCH /api/v1/api-keys/{id} - Update
|
|
506
|
+
// ============================================
|
|
507
|
+
|
|
508
|
+
describe('PATCH /api/v1/api-keys/{id} - Update API Key', () => {
|
|
509
|
+
let testApiKeyId: string
|
|
510
|
+
|
|
511
|
+
beforeEach(() => {
|
|
512
|
+
// Create a test API key for PATCH tests
|
|
513
|
+
const apiKeyData = {
|
|
514
|
+
name: 'Cypress Test Key for PATCH',
|
|
515
|
+
scopes: ['users:read']
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
cy.then(() => {
|
|
519
|
+
return apiKeysAPI.createApiKey(apiKeyData)
|
|
520
|
+
}).then((response: any) => {
|
|
521
|
+
if (response.status === 201) {
|
|
522
|
+
testApiKeyId = response.body.data.id
|
|
523
|
+
testApiKeys.push(testApiKeyId)
|
|
524
|
+
}
|
|
525
|
+
})
|
|
526
|
+
})
|
|
527
|
+
|
|
528
|
+
it('APIKEY_040: Should update API key name', () => {
|
|
529
|
+
cy.then(() => testApiKeyId).then((id) => {
|
|
530
|
+
if (!id) {
|
|
531
|
+
cy.log('No test API key available, skipping')
|
|
532
|
+
return
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
const updateData = { name: 'Cypress Updated Name APIKEY_040' }
|
|
536
|
+
|
|
537
|
+
apiKeysAPI.updateApiKey(id, updateData).then((response: any) => {
|
|
538
|
+
expect(response.status).to.eq(200)
|
|
539
|
+
expect(response.body).to.have.property('success', true)
|
|
540
|
+
expect(response.body.data).to.have.property('name', updateData.name)
|
|
541
|
+
expect(response.body.data).to.have.property('updatedAt')
|
|
542
|
+
})
|
|
543
|
+
})
|
|
544
|
+
})
|
|
545
|
+
|
|
546
|
+
it('APIKEY_041: Should update API key status to inactive', () => {
|
|
547
|
+
cy.then(() => testApiKeyId).then((id) => {
|
|
548
|
+
if (!id) {
|
|
549
|
+
cy.log('No test API key available, skipping')
|
|
550
|
+
return
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
const updateData = { status: 'inactive' }
|
|
554
|
+
|
|
555
|
+
apiKeysAPI.updateApiKey(id, updateData).then((response: any) => {
|
|
556
|
+
expect(response.status).to.eq(200)
|
|
557
|
+
expect(response.body).to.have.property('success', true)
|
|
558
|
+
expect(response.body.data).to.have.property('status', 'inactive')
|
|
559
|
+
})
|
|
560
|
+
})
|
|
561
|
+
})
|
|
562
|
+
|
|
563
|
+
it('APIKEY_042: Should update multiple fields at once', () => {
|
|
564
|
+
cy.then(() => testApiKeyId).then((id) => {
|
|
565
|
+
if (!id) {
|
|
566
|
+
cy.log('No test API key available, skipping')
|
|
567
|
+
return
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
const updateData = {
|
|
571
|
+
name: 'Cypress Multi Update APIKEY_042',
|
|
572
|
+
status: 'active'
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
apiKeysAPI.updateApiKey(id, updateData).then((response: any) => {
|
|
576
|
+
expect(response.status).to.eq(200)
|
|
577
|
+
expect(response.body.data).to.have.property('name', updateData.name)
|
|
578
|
+
expect(response.body.data).to.have.property('status', updateData.status)
|
|
579
|
+
})
|
|
580
|
+
})
|
|
581
|
+
})
|
|
582
|
+
|
|
583
|
+
it('APIKEY_043: Should return 404 for non-existent API key ID', () => {
|
|
584
|
+
const nonExistentId = 'non-existent-id-12345'
|
|
585
|
+
const updateData = { name: 'Updated Name' }
|
|
586
|
+
|
|
587
|
+
apiKeysAPI.updateApiKey(nonExistentId, updateData).then((response: any) => {
|
|
588
|
+
expect(response.status).to.eq(404)
|
|
589
|
+
expect(response.body).to.have.property('success', false)
|
|
590
|
+
expect(response.body).to.have.property('code', 'API_KEY_NOT_FOUND')
|
|
591
|
+
})
|
|
592
|
+
})
|
|
593
|
+
|
|
594
|
+
it('APIKEY_044: Should reject empty update body', () => {
|
|
595
|
+
cy.then(() => testApiKeyId).then((id) => {
|
|
596
|
+
if (!id) {
|
|
597
|
+
cy.log('No test API key available, skipping')
|
|
598
|
+
return
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
apiKeysAPI.updateApiKey(id, {}).then((response: any) => {
|
|
602
|
+
expect(response.status).to.eq(400)
|
|
603
|
+
expect(response.body).to.have.property('success', false)
|
|
604
|
+
expect(response.body).to.have.property('code', 'NO_FIELDS')
|
|
605
|
+
})
|
|
606
|
+
})
|
|
607
|
+
})
|
|
608
|
+
|
|
609
|
+
it('APIKEY_045: Should reject invalid name type', () => {
|
|
610
|
+
cy.then(() => testApiKeyId).then((id) => {
|
|
611
|
+
if (!id) {
|
|
612
|
+
cy.log('No test API key available, skipping')
|
|
613
|
+
return
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
const updateData = { name: 123 }
|
|
617
|
+
|
|
618
|
+
apiKeysAPI.updateApiKey(id, updateData).then((response: any) => {
|
|
619
|
+
expect(response.status).to.eq(400)
|
|
620
|
+
expect(response.body).to.have.property('success', false)
|
|
621
|
+
})
|
|
622
|
+
})
|
|
623
|
+
})
|
|
624
|
+
|
|
625
|
+
it('APIKEY_046: Should reject invalid status value', () => {
|
|
626
|
+
cy.then(() => testApiKeyId).then((id) => {
|
|
627
|
+
if (!id) {
|
|
628
|
+
cy.log('No test API key available, skipping')
|
|
629
|
+
return
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
const updateData = { status: 'invalid_status' }
|
|
633
|
+
|
|
634
|
+
apiKeysAPI.updateApiKey(id, updateData).then((response: any) => {
|
|
635
|
+
expect(response.status).to.eq(400)
|
|
636
|
+
expect(response.body).to.have.property('success', false)
|
|
637
|
+
})
|
|
638
|
+
})
|
|
639
|
+
})
|
|
640
|
+
|
|
641
|
+
it('APIKEY_047: Should reject update without authorization', () => {
|
|
642
|
+
const unauthenticatedAPI = new ApiKeysAPIController(BASE_URL, null)
|
|
643
|
+
|
|
644
|
+
cy.then(() => testApiKeyId).then((id) => {
|
|
645
|
+
if (!id) {
|
|
646
|
+
cy.log('No test API key available, skipping')
|
|
647
|
+
return
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
const updateData = { name: 'Unauthorized Update' }
|
|
651
|
+
|
|
652
|
+
unauthenticatedAPI.updateApiKey(id, updateData).then((response: any) => {
|
|
653
|
+
expect(response.status).to.eq(401)
|
|
654
|
+
expect(response.body).to.have.property('success', false)
|
|
655
|
+
})
|
|
656
|
+
})
|
|
657
|
+
})
|
|
658
|
+
})
|
|
659
|
+
|
|
660
|
+
// ============================================
|
|
661
|
+
// DELETE /api/v1/api-keys/{id} - Revoke
|
|
662
|
+
// ============================================
|
|
663
|
+
|
|
664
|
+
describe('DELETE /api/v1/api-keys/{id} - Revoke API Key', () => {
|
|
665
|
+
let testApiKeyId: string
|
|
666
|
+
|
|
667
|
+
beforeEach(() => {
|
|
668
|
+
// Create a test API key for DELETE tests
|
|
669
|
+
const apiKeyData = {
|
|
670
|
+
name: 'Cypress Test Key for DELETE',
|
|
671
|
+
scopes: ['users:read']
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
cy.then(() => {
|
|
675
|
+
return apiKeysAPI.createApiKey(apiKeyData)
|
|
676
|
+
}).then((response: any) => {
|
|
677
|
+
if (response.status === 201) {
|
|
678
|
+
testApiKeyId = response.body.data.id
|
|
679
|
+
// Don't add to testApiKeys since we're deleting it in the test
|
|
680
|
+
}
|
|
681
|
+
})
|
|
682
|
+
})
|
|
683
|
+
|
|
684
|
+
it('APIKEY_050: Should revoke API key by valid ID', () => {
|
|
685
|
+
cy.then(() => testApiKeyId).then((id) => {
|
|
686
|
+
if (!id) {
|
|
687
|
+
cy.log('No test API key available, skipping')
|
|
688
|
+
return
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
apiKeysAPI.deleteApiKey(id).then((response: any) => {
|
|
692
|
+
expect(response.status).to.eq(200)
|
|
693
|
+
expect(response.body).to.have.property('success', true)
|
|
694
|
+
expect(response.body.data).to.have.property('revoked', true)
|
|
695
|
+
expect(response.body.data).to.have.property('id', id)
|
|
696
|
+
})
|
|
697
|
+
})
|
|
698
|
+
})
|
|
699
|
+
|
|
700
|
+
it('APIKEY_051: Should return 404 for non-existent API key ID', () => {
|
|
701
|
+
const nonExistentId = 'non-existent-id-12345'
|
|
702
|
+
|
|
703
|
+
apiKeysAPI.deleteApiKey(nonExistentId).then((response: any) => {
|
|
704
|
+
expect(response.status).to.eq(404)
|
|
705
|
+
expect(response.body).to.have.property('success', false)
|
|
706
|
+
expect(response.body).to.have.property('code', 'API_KEY_NOT_FOUND')
|
|
707
|
+
})
|
|
708
|
+
|
|
709
|
+
// Cleanup the created key since we didn't delete it
|
|
710
|
+
cy.then(() => testApiKeyId).then((id) => {
|
|
711
|
+
if (id) {
|
|
712
|
+
testApiKeys.push(id)
|
|
713
|
+
}
|
|
714
|
+
})
|
|
715
|
+
})
|
|
716
|
+
|
|
717
|
+
it('APIKEY_052: Should reject revoke without authorization', () => {
|
|
718
|
+
const unauthenticatedAPI = new ApiKeysAPIController(BASE_URL, null)
|
|
719
|
+
|
|
720
|
+
cy.then(() => testApiKeyId).then((id) => {
|
|
721
|
+
if (!id) {
|
|
722
|
+
cy.log('No test API key available, skipping')
|
|
723
|
+
return
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
unauthenticatedAPI.deleteApiKey(id).then((response: any) => {
|
|
727
|
+
expect(response.status).to.eq(401)
|
|
728
|
+
expect(response.body).to.have.property('success', false)
|
|
729
|
+
|
|
730
|
+
// Cleanup since the delete failed
|
|
731
|
+
testApiKeys.push(id)
|
|
732
|
+
})
|
|
733
|
+
})
|
|
734
|
+
})
|
|
735
|
+
|
|
736
|
+
it('APIKEY_053: Should reject revoke with invalid API key', () => {
|
|
737
|
+
const invalidAPI = new ApiKeysAPIController(BASE_URL, 'invalid_key')
|
|
738
|
+
|
|
739
|
+
cy.then(() => testApiKeyId).then((id) => {
|
|
740
|
+
if (!id) {
|
|
741
|
+
cy.log('No test API key available, skipping')
|
|
742
|
+
return
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
invalidAPI.deleteApiKey(id).then((response: any) => {
|
|
746
|
+
expect(response.status).to.eq(401)
|
|
747
|
+
expect(response.body).to.have.property('success', false)
|
|
748
|
+
|
|
749
|
+
// Cleanup since the delete failed
|
|
750
|
+
testApiKeys.push(id)
|
|
751
|
+
})
|
|
752
|
+
})
|
|
753
|
+
})
|
|
754
|
+
|
|
755
|
+
it('APIKEY_054: Should verify revoked key status after deletion', () => {
|
|
756
|
+
cy.then(() => testApiKeyId).then((id) => {
|
|
757
|
+
if (!id) {
|
|
758
|
+
cy.log('No test API key available, skipping')
|
|
759
|
+
return
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// First revoke the key
|
|
763
|
+
apiKeysAPI.deleteApiKey(id).then((response: any) => {
|
|
764
|
+
expect(response.status).to.eq(200)
|
|
765
|
+
expect(response.body.data).to.have.property('revoked', true)
|
|
766
|
+
|
|
767
|
+
// Then verify it's inactive
|
|
768
|
+
apiKeysAPI.getApiKeyById(id).then((getResponse: any) => {
|
|
769
|
+
if (getResponse.status === 200) {
|
|
770
|
+
expect(getResponse.body.data.status).to.eq('inactive')
|
|
771
|
+
}
|
|
772
|
+
// If 404, that's also acceptable (hard delete)
|
|
773
|
+
})
|
|
774
|
+
})
|
|
775
|
+
})
|
|
776
|
+
})
|
|
777
|
+
})
|
|
778
|
+
|
|
779
|
+
// ============================================
|
|
780
|
+
// Integration Tests
|
|
781
|
+
// ============================================
|
|
782
|
+
|
|
783
|
+
describe('Integration - Complete API Key Lifecycle', () => {
|
|
784
|
+
|
|
785
|
+
it('APIKEY_100: Should complete full lifecycle: Create -> Read -> Update -> Revoke -> Verify', () => {
|
|
786
|
+
let createdApiKeyId: string
|
|
787
|
+
|
|
788
|
+
// 1. Create API key
|
|
789
|
+
const initialData = {
|
|
790
|
+
name: 'Cypress Lifecycle Test Key',
|
|
791
|
+
scopes: ['users:read', 'tasks:read'],
|
|
792
|
+
expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString()
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
apiKeysAPI.createApiKey(initialData).then((createResponse: any) => {
|
|
796
|
+
if (createResponse.status === 429) {
|
|
797
|
+
cy.log('Rate limit reached, skipping integration test')
|
|
798
|
+
return
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
expect(createResponse.status).to.eq(201)
|
|
802
|
+
expect(createResponse.body).to.have.property('success', true)
|
|
803
|
+
expect(createResponse.body.data).to.have.property('key')
|
|
804
|
+
|
|
805
|
+
createdApiKeyId = createResponse.body.data.id
|
|
806
|
+
expect(createResponse.body.data.name).to.eq(initialData.name)
|
|
807
|
+
|
|
808
|
+
// 2. Read API key
|
|
809
|
+
return apiKeysAPI.getApiKeyById(createdApiKeyId)
|
|
810
|
+
}).then((readResponse: any) => {
|
|
811
|
+
if (!readResponse) return
|
|
812
|
+
|
|
813
|
+
expect(readResponse.status).to.eq(200)
|
|
814
|
+
expect(readResponse.body.data.id).to.eq(createdApiKeyId)
|
|
815
|
+
expect(readResponse.body.data.name).to.eq(initialData.name)
|
|
816
|
+
expect(readResponse.body.data).to.have.property('usage_stats')
|
|
817
|
+
|
|
818
|
+
// 3. Update API key
|
|
819
|
+
const updateData = {
|
|
820
|
+
name: 'Cypress Updated Lifecycle Key',
|
|
821
|
+
status: 'active'
|
|
822
|
+
}
|
|
823
|
+
return apiKeysAPI.updateApiKey(createdApiKeyId, updateData)
|
|
824
|
+
}).then((updateResponse: any) => {
|
|
825
|
+
if (!updateResponse) return
|
|
826
|
+
|
|
827
|
+
expect(updateResponse.status).to.eq(200)
|
|
828
|
+
expect(updateResponse.body.data.name).to.eq('Cypress Updated Lifecycle Key')
|
|
829
|
+
expect(updateResponse.body.data.status).to.eq('active')
|
|
830
|
+
|
|
831
|
+
// 4. Revoke API key
|
|
832
|
+
return apiKeysAPI.deleteApiKey(createdApiKeyId)
|
|
833
|
+
}).then((deleteResponse: any) => {
|
|
834
|
+
if (!deleteResponse) return
|
|
835
|
+
|
|
836
|
+
expect(deleteResponse.status).to.eq(200)
|
|
837
|
+
expect(deleteResponse.body.data.revoked).to.be.true
|
|
838
|
+
|
|
839
|
+
// 5. Verify revoked status
|
|
840
|
+
return apiKeysAPI.getApiKeyById(createdApiKeyId)
|
|
841
|
+
}).then((finalResponse: any) => {
|
|
842
|
+
if (!finalResponse) return
|
|
843
|
+
|
|
844
|
+
if (finalResponse.status === 200) {
|
|
845
|
+
expect(finalResponse.body.data.status).to.eq('inactive')
|
|
846
|
+
}
|
|
847
|
+
// 404 is also acceptable if hard delete
|
|
848
|
+
})
|
|
849
|
+
})
|
|
850
|
+
|
|
851
|
+
it('APIKEY_101: Should handle sequential operations correctly', () => {
|
|
852
|
+
const apiKeysCreated: string[] = []
|
|
853
|
+
|
|
854
|
+
// Create multiple API keys
|
|
855
|
+
const createKey = (name: string) => {
|
|
856
|
+
return apiKeysAPI.createApiKey({
|
|
857
|
+
name: name,
|
|
858
|
+
scopes: ['users:read']
|
|
859
|
+
}).then((response: any) => {
|
|
860
|
+
if (response.status === 201) {
|
|
861
|
+
apiKeysCreated.push(response.body.data.id)
|
|
862
|
+
return response.body.data.id
|
|
863
|
+
}
|
|
864
|
+
return null
|
|
865
|
+
})
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
createKey('Cypress Sequential Key 1').then(() => {
|
|
869
|
+
return createKey('Cypress Sequential Key 2')
|
|
870
|
+
}).then(() => {
|
|
871
|
+
// List should contain our keys
|
|
872
|
+
return apiKeysAPI.getApiKeys()
|
|
873
|
+
}).then((listResponse: any) => {
|
|
874
|
+
expect(listResponse.status).to.eq(200)
|
|
875
|
+
expect(listResponse.body.data.length).to.be.greaterThan(0)
|
|
876
|
+
|
|
877
|
+
// Cleanup
|
|
878
|
+
apiKeysCreated.forEach(id => {
|
|
879
|
+
testApiKeys.push(id)
|
|
880
|
+
})
|
|
881
|
+
})
|
|
882
|
+
})
|
|
883
|
+
})
|
|
884
|
+
|
|
885
|
+
// ============================================
|
|
886
|
+
// CORS Preflight Tests
|
|
887
|
+
// ============================================
|
|
888
|
+
|
|
889
|
+
describe('OPTIONS - CORS Preflight', () => {
|
|
890
|
+
|
|
891
|
+
it('APIKEY_110: Should handle CORS preflight requests for API keys list', () => {
|
|
892
|
+
cy.request({
|
|
893
|
+
method: 'OPTIONS',
|
|
894
|
+
url: `${BASE_URL}/api/v1/api-keys`,
|
|
895
|
+
failOnStatusCode: false
|
|
896
|
+
}).then((response: any) => {
|
|
897
|
+
expect(response.status).to.eq(200)
|
|
898
|
+
|
|
899
|
+
// Verify CORS headers
|
|
900
|
+
expect(response.headers).to.have.property('access-control-allow-origin')
|
|
901
|
+
expect(response.headers).to.have.property('access-control-allow-methods')
|
|
902
|
+
expect(response.headers).to.have.property('access-control-allow-headers')
|
|
903
|
+
})
|
|
904
|
+
})
|
|
905
|
+
|
|
906
|
+
it('APIKEY_111: Should handle CORS preflight requests for specific API key', () => {
|
|
907
|
+
const testId = 'test-id-for-options'
|
|
908
|
+
|
|
909
|
+
cy.request({
|
|
910
|
+
method: 'OPTIONS',
|
|
911
|
+
url: `${BASE_URL}/api/v1/api-keys/${testId}`,
|
|
912
|
+
failOnStatusCode: false
|
|
913
|
+
}).then((response: any) => {
|
|
914
|
+
expect(response.status).to.eq(200)
|
|
915
|
+
|
|
916
|
+
// Verify CORS headers
|
|
917
|
+
expect(response.headers).to.have.property('access-control-allow-origin')
|
|
918
|
+
expect(response.headers).to.have.property('access-control-allow-methods')
|
|
919
|
+
expect(response.headers).to.have.property('access-control-allow-headers')
|
|
920
|
+
})
|
|
921
|
+
})
|
|
922
|
+
})
|
|
923
|
+
})
|