@nextsparkjs/ai-workflow 0.1.0-beta.100
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/LICENSE +21 -0
- package/README.md +115 -0
- package/claude/_docs/workflows-optimizations.md +359 -0
- package/claude/agents/api-tester.md +634 -0
- package/claude/agents/architecture-supervisor.md +1351 -0
- package/claude/agents/backend-developer.md +997 -0
- package/claude/agents/backend-validator.md +417 -0
- package/claude/agents/bdd-docs-writer.md +737 -0
- package/claude/agents/block-developer.md +677 -0
- package/claude/agents/code-reviewer.md +1432 -0
- package/claude/agents/db-developer.md +721 -0
- package/claude/agents/db-validator.md +407 -0
- package/claude/agents/demo-video-generator.md +493 -0
- package/claude/agents/documentation-writer.md +1268 -0
- package/claude/agents/frontend-developer.md +1234 -0
- package/claude/agents/frontend-validator.md +777 -0
- package/claude/agents/functional-validator.md +630 -0
- package/claude/agents/mock-analyst.md +387 -0
- package/claude/agents/product-manager.md +963 -0
- package/claude/agents/qa-automation.md +1762 -0
- package/claude/agents/release-manager.md +634 -0
- package/claude/agents/selectors-translator.md +262 -0
- package/claude/agents/unit-test-writer.md +785 -0
- package/claude/agents/visual-comparator.md +329 -0
- package/claude/agents/workflow-maintainer.md +352 -0
- package/claude/commands/do/README.md +88 -0
- package/claude/commands/do/create-api.md +64 -0
- package/claude/commands/do/create-entity.md +66 -0
- package/claude/commands/do/create-migration.md +64 -0
- package/claude/commands/do/create-plugin.md +56 -0
- package/claude/commands/do/create-theme.md +70 -0
- package/claude/commands/do/mock-data.md +67 -0
- package/claude/commands/do/reset-db.md +71 -0
- package/claude/commands/do/setup-scheduled-action.md +75 -0
- package/claude/commands/do/sync-code-review.md +117 -0
- package/claude/commands/do/update-selectors.md +112 -0
- package/claude/commands/do/use-skills.md +90 -0
- package/claude/commands/do/validate-blocks.md +69 -0
- package/claude/commands/how-to/README.md +261 -0
- package/claude/commands/how-to/add-metadata.md +692 -0
- package/claude/commands/how-to/add-taxonomies.md +806 -0
- package/claude/commands/how-to/add-translations.md +571 -0
- package/claude/commands/how-to/create-api.md +577 -0
- package/claude/commands/how-to/create-block.md +575 -0
- package/claude/commands/how-to/create-child-entities.md +771 -0
- package/claude/commands/how-to/create-entity.md +597 -0
- package/claude/commands/how-to/create-migrations.md +605 -0
- package/claude/commands/how-to/create-plugin.md +654 -0
- package/claude/commands/how-to/customize-app.md +481 -0
- package/claude/commands/how-to/customize-dashboard.md +553 -0
- package/claude/commands/how-to/customize-theme.md +438 -0
- package/claude/commands/how-to/define-features-flows.md +632 -0
- package/claude/commands/how-to/deploy.md +507 -0
- package/claude/commands/how-to/handle-file-uploads.md +746 -0
- package/claude/commands/how-to/implement-search.md +1001 -0
- package/claude/commands/how-to/install-plugins.md +352 -0
- package/claude/commands/how-to/manage-test-coverage.md +984 -0
- package/claude/commands/how-to/run-tests.md +400 -0
- package/claude/commands/how-to/set-app-languages.md +601 -0
- package/claude/commands/how-to/set-plans-and-permissions.md +575 -0
- package/claude/commands/how-to/set-scheduled-actions.md +527 -0
- package/claude/commands/how-to/set-user-roles-and-permissions.md +550 -0
- package/claude/commands/how-to/setup-authentication.md +388 -0
- package/claude/commands/how-to/setup-claude-code.md +440 -0
- package/claude/commands/how-to/setup-database.md +274 -0
- package/claude/commands/how-to/setup-email-providers.md +598 -0
- package/claude/commands/how-to/setup-mobile-dev.md +627 -0
- package/claude/commands/how-to/start.md +500 -0
- package/claude/commands/how-to/use-devtools.md +639 -0
- package/claude/commands/how-to/use-superadmin.md +622 -0
- package/claude/commands/session/README.md +193 -0
- package/claude/commands/session/block-create.md +190 -0
- package/claude/commands/session/block-list.md +203 -0
- package/claude/commands/session/block-update.md +192 -0
- package/claude/commands/session/block-validate.md +218 -0
- package/claude/commands/session/changelog.md +115 -0
- package/claude/commands/session/close.md +225 -0
- package/claude/commands/session/commit.md +174 -0
- package/claude/commands/session/db-entity.md +206 -0
- package/claude/commands/session/db-fix.md +212 -0
- package/claude/commands/session/db-sample.md +206 -0
- package/claude/commands/session/demo.md +178 -0
- package/claude/commands/session/doc-bdd.md +207 -0
- package/claude/commands/session/doc-feature.md +218 -0
- package/claude/commands/session/doc-read.md +225 -0
- package/claude/commands/session/execute.md +204 -0
- package/claude/commands/session/explain.md +202 -0
- package/claude/commands/session/fix-bug.md +210 -0
- package/claude/commands/session/fix-build.md +182 -0
- package/claude/commands/session/fix-test.md +189 -0
- package/claude/commands/session/pending.md +232 -0
- package/claude/commands/session/refine.md +188 -0
- package/claude/commands/session/resume.md +192 -0
- package/claude/commands/session/review.md +192 -0
- package/claude/commands/session/scope-change.md +181 -0
- package/claude/commands/session/start-blocks.md +347 -0
- package/claude/commands/session/start.md +604 -0
- package/claude/commands/session/status.md +169 -0
- package/claude/commands/session/test-fix.md +221 -0
- package/claude/commands/session/test-run.md +203 -0
- package/claude/commands/session/test-write.md +242 -0
- package/claude/commands/session/validate.md +162 -0
- package/claude/config/context.json +40 -0
- package/claude/config/github.json +69 -0
- package/claude/config/github.schema.json +106 -0
- package/claude/config/team.json +46 -0
- package/claude/config/team.schema.json +106 -0
- package/claude/config/workspace.json +43 -0
- package/claude/config/workspace.schema.json +75 -0
- package/claude/skills/README.md +228 -0
- package/claude/skills/accessibility/SKILL.md +573 -0
- package/claude/skills/api-bypass-layers/SKILL.md +550 -0
- package/claude/skills/asana-integration/SKILL.md +499 -0
- package/claude/skills/better-auth/SKILL.md +666 -0
- package/claude/skills/billing-subscriptions/SKILL.md +660 -0
- package/claude/skills/block-decision-matrix/SKILL.md +359 -0
- package/claude/skills/clickup-integration/SKILL.md +434 -0
- package/claude/skills/core-theme-responsibilities/SKILL.md +485 -0
- package/claude/skills/create-plugin/SKILL.md +425 -0
- package/claude/skills/create-theme/SKILL.md +331 -0
- package/claude/skills/cypress-api/SKILL.md +511 -0
- package/claude/skills/cypress-api/scripts/generate-api-controller.py +329 -0
- package/claude/skills/cypress-api/scripts/generate-api-test.py +930 -0
- package/claude/skills/cypress-e2e/SKILL.md +526 -0
- package/claude/skills/cypress-e2e/scripts/extract-selectors.py +383 -0
- package/claude/skills/cypress-e2e/scripts/generate-uat-test.py +788 -0
- package/claude/skills/cypress-selectors/SKILL.md +309 -0
- package/claude/skills/cypress-selectors/scripts/extract-missing.py +243 -0
- package/claude/skills/cypress-selectors/scripts/generate-block-selectors.py +283 -0
- package/claude/skills/cypress-selectors/scripts/validate-selectors.py +145 -0
- package/claude/skills/database-migrations/SKILL.md +335 -0
- package/claude/skills/database-migrations/scripts/generate-sample-data.py +284 -0
- package/claude/skills/database-migrations/scripts/validate-migration.py +323 -0
- package/claude/skills/design-system/SKILL.md +682 -0
- package/claude/skills/documentation/SKILL.md +540 -0
- package/claude/skills/entity-api/SKILL.md +482 -0
- package/claude/skills/entity-system/SKILL.md +635 -0
- package/claude/skills/entity-system/scripts/generate-child-migration.py +298 -0
- package/claude/skills/entity-system/scripts/generate-metas-migration.py +233 -0
- package/claude/skills/entity-system/scripts/generate-migration.py +382 -0
- package/claude/skills/entity-system/scripts/generate-sample-data.py +418 -0
- package/claude/skills/entity-system/scripts/scaffold-entity.py +661 -0
- package/claude/skills/github/SKILL.md +467 -0
- package/claude/skills/i18n-nextintl/SKILL.md +302 -0
- package/claude/skills/i18n-nextintl/scripts/add-translation.py +243 -0
- package/claude/skills/i18n-nextintl/scripts/extract-hardcoded.py +246 -0
- package/claude/skills/i18n-nextintl/scripts/validate-translations.py +260 -0
- package/claude/skills/impact-analysis/SKILL.md +203 -0
- package/claude/skills/jest-unit/SKILL.md +306 -0
- package/claude/skills/jest-unit/references/component-testing.md +371 -0
- package/claude/skills/jest-unit/references/mocking-patterns.md +380 -0
- package/claude/skills/jest-unit/references/service-hook-testing.md +454 -0
- package/claude/skills/jira-integration/SKILL.md +539 -0
- package/claude/skills/media-library/SKILL.md +743 -0
- package/claude/skills/mock-analysis/SKILL.md +276 -0
- package/claude/skills/monorepo-architecture/SKILL.md +162 -0
- package/claude/skills/nextjs-api-development/SKILL.md +364 -0
- package/claude/skills/nextjs-api-development/scripts/generate-crud-tests.py +456 -0
- package/claude/skills/nextjs-api-development/scripts/scaffold-endpoint.py +481 -0
- package/claude/skills/nextjs-api-development/scripts/validate-api.py +283 -0
- package/claude/skills/notion-integration/SKILL.md +641 -0
- package/claude/skills/npm-development-workflow/SKILL.md +480 -0
- package/claude/skills/page-builder-blocks/SKILL.md +530 -0
- package/claude/skills/page-builder-blocks/scripts/scaffold-block.py +444 -0
- package/claude/skills/permissions-system/SKILL.md +619 -0
- package/claude/skills/plugins/SKILL.md +340 -0
- package/claude/skills/plugins/references/plugin-templates.md +414 -0
- package/claude/skills/plugins/references/plugin-testing.md +353 -0
- package/claude/skills/plugins/references/plugin-types.md +198 -0
- package/claude/skills/plugins/scripts/scaffold-plugin.py +443 -0
- package/claude/skills/pom-patterns/SKILL.md +452 -0
- package/claude/skills/pom-patterns/scripts/generate-pom.py +392 -0
- package/claude/skills/rate-limiting/SKILL.md +342 -0
- package/claude/skills/react-best-practices/AGENTS.md +2410 -0
- package/claude/skills/react-best-practices/README.md +123 -0
- package/claude/skills/react-best-practices/SKILL.md +125 -0
- package/claude/skills/react-best-practices/metadata.json +15 -0
- package/claude/skills/react-best-practices/rules/_sections.md +46 -0
- package/claude/skills/react-best-practices/rules/_template.md +28 -0
- package/claude/skills/react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/claude/skills/react-best-practices/rules/advanced-use-latest.md +49 -0
- package/claude/skills/react-best-practices/rules/async-api-routes.md +38 -0
- package/claude/skills/react-best-practices/rules/async-defer-await.md +80 -0
- package/claude/skills/react-best-practices/rules/async-dependencies.md +36 -0
- package/claude/skills/react-best-practices/rules/async-parallel.md +28 -0
- package/claude/skills/react-best-practices/rules/async-suspense-boundaries.md +99 -0
- package/claude/skills/react-best-practices/rules/bundle-barrel-imports.md +59 -0
- package/claude/skills/react-best-practices/rules/bundle-conditional.md +31 -0
- package/claude/skills/react-best-practices/rules/bundle-defer-third-party.md +49 -0
- package/claude/skills/react-best-practices/rules/bundle-dynamic-imports.md +35 -0
- package/claude/skills/react-best-practices/rules/bundle-preload.md +50 -0
- package/claude/skills/react-best-practices/rules/client-event-listeners.md +74 -0
- package/claude/skills/react-best-practices/rules/client-localstorage-schema.md +71 -0
- package/claude/skills/react-best-practices/rules/client-passive-event-listeners.md +48 -0
- package/claude/skills/react-best-practices/rules/client-swr-dedup.md +56 -0
- package/claude/skills/react-best-practices/rules/js-batch-dom-css.md +82 -0
- package/claude/skills/react-best-practices/rules/js-cache-function-results.md +80 -0
- package/claude/skills/react-best-practices/rules/js-cache-property-access.md +28 -0
- package/claude/skills/react-best-practices/rules/js-cache-storage.md +70 -0
- package/claude/skills/react-best-practices/rules/js-combine-iterations.md +32 -0
- package/claude/skills/react-best-practices/rules/js-early-exit.md +50 -0
- package/claude/skills/react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/claude/skills/react-best-practices/rules/js-index-maps.md +37 -0
- package/claude/skills/react-best-practices/rules/js-length-check-first.md +49 -0
- package/claude/skills/react-best-practices/rules/js-min-max-loop.md +82 -0
- package/claude/skills/react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/claude/skills/react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/claude/skills/react-best-practices/rules/rendering-activity.md +26 -0
- package/claude/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
- package/claude/skills/react-best-practices/rules/rendering-conditional-render.md +40 -0
- package/claude/skills/react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/claude/skills/react-best-practices/rules/rendering-hoist-jsx.md +46 -0
- package/claude/skills/react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
- package/claude/skills/react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/claude/skills/react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/claude/skills/react-best-practices/rules/rerender-dependencies.md +45 -0
- package/claude/skills/react-best-practices/rules/rerender-derived-state.md +29 -0
- package/claude/skills/react-best-practices/rules/rerender-functional-setstate.md +74 -0
- package/claude/skills/react-best-practices/rules/rerender-lazy-state-init.md +58 -0
- package/claude/skills/react-best-practices/rules/rerender-memo.md +44 -0
- package/claude/skills/react-best-practices/rules/rerender-transitions.md +40 -0
- package/claude/skills/react-best-practices/rules/server-after-nonblocking.md +73 -0
- package/claude/skills/react-best-practices/rules/server-cache-lru.md +41 -0
- package/claude/skills/react-best-practices/rules/server-cache-react.md +76 -0
- package/claude/skills/react-best-practices/rules/server-parallel-fetching.md +83 -0
- package/claude/skills/react-best-practices/rules/server-serialization.md +38 -0
- package/claude/skills/react-patterns/SKILL.md +688 -0
- package/claude/skills/registry-system/SKILL.md +331 -0
- package/claude/skills/scheduled-actions/SKILL.md +671 -0
- package/claude/skills/scope-enforcement/SKILL.md +542 -0
- package/claude/skills/scope-enforcement/scripts/validate-scope.py +357 -0
- package/claude/skills/server-actions/SKILL.md +493 -0
- package/claude/skills/service-layer/SKILL.md +587 -0
- package/claude/skills/session-management/SKILL.md +266 -0
- package/claude/skills/session-management/scripts/create-session.py +166 -0
- package/claude/skills/session-management/scripts/iteration-close.sh +105 -0
- package/claude/skills/session-management/scripts/iteration-init.sh +180 -0
- package/claude/skills/session-management/scripts/session-archive.sh +87 -0
- package/claude/skills/session-management/scripts/session-close.sh +133 -0
- package/claude/skills/session-management/scripts/session-init.sh +225 -0
- package/claude/skills/session-management/scripts/session-list.sh +163 -0
- package/claude/skills/session-management/scripts/split-plan.sh +116 -0
- package/claude/skills/shadcn-components/SKILL.md +586 -0
- package/claude/skills/shadcn-theming/SKILL.md +446 -0
- package/claude/skills/suspense-loading/SKILL.md +280 -0
- package/claude/skills/tailwind-theming/SKILL.md +507 -0
- package/claude/skills/tanstack-query/SKILL.md +608 -0
- package/claude/skills/test-coverage/SKILL.md +239 -0
- package/claude/skills/web-design-guidelines/SKILL.md +39 -0
- package/claude/skills/zod-validation/SKILL.md +537 -0
- package/claude/templates/blocks/progress.md +86 -0
- package/claude/templates/iteration/changes.md +61 -0
- package/claude/templates/iteration/progress.md +55 -0
- package/claude/templates/log.md +31 -0
- package/claude/templates/story/context.md +77 -0
- package/claude/templates/story/pendings.md +37 -0
- package/claude/templates/story/plan.md +299 -0
- package/claude/templates/story/requirements.md +109 -0
- package/claude/templates/story/scope.json +10 -0
- package/claude/templates/story/tests.md +91 -0
- package/claude/templates/task/progress.md +58 -0
- package/claude/templates/task/requirements.md +54 -0
- package/claude/workflows/README.md +154 -0
- package/claude/workflows/blocks.md +614 -0
- package/claude/workflows/story.md +1207 -0
- package/claude/workflows/task.md +927 -0
- package/claude/workflows/tweak.md +527 -0
- package/cursor/.gitkeep +0 -0
- package/package.json +35 -0
- package/scripts/postinstall.mjs +198 -0
- package/scripts/setup.mjs +282 -0
- package/scripts/sync.mjs +209 -0
|
@@ -0,0 +1,930 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Generate API Test Script
|
|
4
|
+
|
|
5
|
+
Generates an API test file and optional BDD documentation for an entity.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
python generate-api-test.py --entity ENTITY [--theme THEME] [--session SESSION] [--with-bdd]
|
|
9
|
+
|
|
10
|
+
Options:
|
|
11
|
+
--entity ENTITY Entity name (e.g., tasks, customers)
|
|
12
|
+
--theme THEME Theme name (default: default)
|
|
13
|
+
--session SESSION Session name for @scope tag
|
|
14
|
+
--with-bdd Generate BDD documentation file
|
|
15
|
+
--dry-run Preview without writing to file
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import os
|
|
19
|
+
import sys
|
|
20
|
+
import re
|
|
21
|
+
import argparse
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from datetime import datetime
|
|
24
|
+
from typing import Optional
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def to_pascal_case(name: str) -> str:
|
|
28
|
+
"""Convert kebab-case/snake_case to PascalCase."""
|
|
29
|
+
return ''.join(x.title() for x in re.split(r'[-_]', name))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def to_singular(name: str) -> str:
|
|
33
|
+
"""Convert plural to singular (simple English rules)."""
|
|
34
|
+
if name.endswith('ies'):
|
|
35
|
+
return name[:-3] + 'y'
|
|
36
|
+
elif name.endswith('es'):
|
|
37
|
+
return name[:-2]
|
|
38
|
+
elif name.endswith('s'):
|
|
39
|
+
return name[:-1]
|
|
40
|
+
return name
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def generate_test_content(
|
|
44
|
+
entity: str,
|
|
45
|
+
theme: str,
|
|
46
|
+
session: Optional[str] = None
|
|
47
|
+
) -> str:
|
|
48
|
+
"""Generate the API test file content."""
|
|
49
|
+
singular = to_singular(entity)
|
|
50
|
+
pascal_singular = to_pascal_case(singular)
|
|
51
|
+
pascal_plural = to_pascal_case(entity)
|
|
52
|
+
entity_upper = entity.upper()
|
|
53
|
+
timestamp = datetime.now().strftime('%Y-%m-%d')
|
|
54
|
+
session_name = session or 'manual'
|
|
55
|
+
|
|
56
|
+
# Build tags
|
|
57
|
+
tags = [f"'@api'", f"'@feat-{entity}'", "'@crud'", "'@regression'"]
|
|
58
|
+
if session:
|
|
59
|
+
tags.append(f"'@scope-{session}'")
|
|
60
|
+
tags_str = ', '.join(tags)
|
|
61
|
+
|
|
62
|
+
return f'''/// <reference types="cypress" />
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* {pascal_plural} API - CRUD Tests
|
|
66
|
+
*
|
|
67
|
+
* Generated: {timestamp}
|
|
68
|
+
* Session: {session_name}
|
|
69
|
+
*
|
|
70
|
+
* Basic CRUD operations for /api/v1/{entity} endpoints
|
|
71
|
+
* Uses superadmin API key for full access with team context
|
|
72
|
+
*/
|
|
73
|
+
|
|
74
|
+
import * as allure from 'allure-cypress'
|
|
75
|
+
|
|
76
|
+
const {pascal_plural}APIController = require('../../src/controllers/{pascal_plural}APIController.js')
|
|
77
|
+
|
|
78
|
+
describe('{pascal_plural} API - CRUD Operations', {{
|
|
79
|
+
tags: [{tags_str}]
|
|
80
|
+
}}, () => {{
|
|
81
|
+
let api: any
|
|
82
|
+
let createdEntities: any[] = []
|
|
83
|
+
|
|
84
|
+
// Superadmin API key for testing
|
|
85
|
+
// TODO: Replace with your test API key from fixtures or environment
|
|
86
|
+
const SUPERADMIN_API_KEY = Cypress.env('SUPERADMIN_API_KEY') || 'sk_test_...'
|
|
87
|
+
const TEAM_ID = Cypress.env('TEAM_ID') || 'team-tmt-001'
|
|
88
|
+
const BASE_URL = Cypress.config('baseUrl') || 'http://localhost:5173'
|
|
89
|
+
|
|
90
|
+
before(() => {{
|
|
91
|
+
// Initialize API controller with superadmin API key and team context
|
|
92
|
+
api = new {pascal_plural}APIController(BASE_URL, SUPERADMIN_API_KEY, TEAM_ID)
|
|
93
|
+
cy.log('{pascal_plural}APIController initialized')
|
|
94
|
+
cy.log(`Base URL: ${{BASE_URL}}`)
|
|
95
|
+
cy.log(`Team ID: ${{TEAM_ID}}`)
|
|
96
|
+
}})
|
|
97
|
+
|
|
98
|
+
beforeEach(() => {{
|
|
99
|
+
allure.epic('API')
|
|
100
|
+
allure.feature('{pascal_plural}')
|
|
101
|
+
}})
|
|
102
|
+
|
|
103
|
+
afterEach(() => {{
|
|
104
|
+
// Cleanup: Delete entities created during tests
|
|
105
|
+
if (createdEntities.length > 0) {{
|
|
106
|
+
createdEntities.forEach((entity: any) => {{
|
|
107
|
+
if (entity && entity.id) {{
|
|
108
|
+
api.delete{pascal_singular}(entity.id)
|
|
109
|
+
}}
|
|
110
|
+
}})
|
|
111
|
+
createdEntities = []
|
|
112
|
+
}}
|
|
113
|
+
}})
|
|
114
|
+
|
|
115
|
+
// ============================================================
|
|
116
|
+
// GET /api/v1/{entity} - List {pascal_plural}
|
|
117
|
+
// ============================================================
|
|
118
|
+
describe('GET /api/v1/{entity} - List {pascal_plural}', () => {{
|
|
119
|
+
it('{entity_upper}_API_001: Should list {entity} with valid API key', {{ tags: '@smoke' }}, () => {{
|
|
120
|
+
allure.story('CRUD Operations')
|
|
121
|
+
allure.severity('critical')
|
|
122
|
+
api.get{pascal_plural}().then((response: any) => {{
|
|
123
|
+
api.validateSuccessResponse(response, 200)
|
|
124
|
+
api.validatePaginatedResponse(response)
|
|
125
|
+
expect(response.body.data).to.be.an('array')
|
|
126
|
+
|
|
127
|
+
cy.log(`Found ${{response.body.data.length}} {entity}`)
|
|
128
|
+
cy.log(`Total: ${{response.body.info.total}}`)
|
|
129
|
+
}})
|
|
130
|
+
}})
|
|
131
|
+
|
|
132
|
+
it('{entity_upper}_API_002: Should list {entity} with pagination', () => {{
|
|
133
|
+
api.get{pascal_plural}({{ page: 1, limit: 5 }}).then((response: any) => {{
|
|
134
|
+
api.validatePaginatedResponse(response)
|
|
135
|
+
expect(response.body.info.page).to.eq(1)
|
|
136
|
+
expect(response.body.info.limit).to.eq(5)
|
|
137
|
+
expect(response.body.data.length).to.be.at.most(5)
|
|
138
|
+
|
|
139
|
+
cy.log(`Page 1, Limit 5: Got ${{response.body.data.length}} {entity}`)
|
|
140
|
+
}})
|
|
141
|
+
}})
|
|
142
|
+
|
|
143
|
+
it('{entity_upper}_API_003: Should reject request without API key', () => {{
|
|
144
|
+
const originalApiKey = api.apiKey
|
|
145
|
+
api.setApiKey(null)
|
|
146
|
+
|
|
147
|
+
api.get{pascal_plural}().then((response: any) => {{
|
|
148
|
+
expect(response.status).to.eq(401)
|
|
149
|
+
expect(response.body.success).to.be.false
|
|
150
|
+
}})
|
|
151
|
+
|
|
152
|
+
api.setApiKey(originalApiKey)
|
|
153
|
+
}})
|
|
154
|
+
|
|
155
|
+
it('{entity_upper}_API_004: Should reject request without x-team-id', () => {{
|
|
156
|
+
const originalTeamId = api.teamId
|
|
157
|
+
api.setTeamId(null)
|
|
158
|
+
|
|
159
|
+
api.get{pascal_plural}().then((response: any) => {{
|
|
160
|
+
expect(response.status).to.eq(400)
|
|
161
|
+
expect(response.body.success).to.be.false
|
|
162
|
+
expect(response.body.code).to.eq('TEAM_CONTEXT_REQUIRED')
|
|
163
|
+
}})
|
|
164
|
+
|
|
165
|
+
api.setTeamId(originalTeamId)
|
|
166
|
+
}})
|
|
167
|
+
}})
|
|
168
|
+
|
|
169
|
+
// ============================================================
|
|
170
|
+
// POST /api/v1/{entity} - Create {pascal_singular}
|
|
171
|
+
// ============================================================
|
|
172
|
+
describe('POST /api/v1/{entity} - Create {pascal_singular}', () => {{
|
|
173
|
+
it('{entity_upper}_API_010: Should create {singular} with valid data', {{ tags: '@smoke' }}, () => {{
|
|
174
|
+
allure.story('CRUD Operations')
|
|
175
|
+
allure.severity('critical')
|
|
176
|
+
|
|
177
|
+
// TODO: Customize test data based on entity schema
|
|
178
|
+
const data = api.generateRandom{pascal_singular}Data({{
|
|
179
|
+
// Add required fields here
|
|
180
|
+
}})
|
|
181
|
+
|
|
182
|
+
api.create{pascal_singular}(data).then((response: any) => {{
|
|
183
|
+
api.validateSuccessResponse(response, 201)
|
|
184
|
+
api.validate{pascal_singular}Object(response.body.data)
|
|
185
|
+
|
|
186
|
+
// TODO: Add entity-specific validations
|
|
187
|
+
// expect(response.body.data.title).to.eq(data.title)
|
|
188
|
+
|
|
189
|
+
createdEntities.push(response.body.data)
|
|
190
|
+
cy.log(`Created {singular}: ${{response.body.data.id}}`)
|
|
191
|
+
}})
|
|
192
|
+
}})
|
|
193
|
+
|
|
194
|
+
it('{entity_upper}_API_011: Should create {singular} with minimal data', () => {{
|
|
195
|
+
// TODO: Customize with minimal required fields
|
|
196
|
+
const data = api.generateRandom{pascal_singular}Data()
|
|
197
|
+
|
|
198
|
+
api.create{pascal_singular}(data).then((response: any) => {{
|
|
199
|
+
api.validateSuccessResponse(response, 201)
|
|
200
|
+
api.validate{pascal_singular}Object(response.body.data)
|
|
201
|
+
|
|
202
|
+
createdEntities.push(response.body.data)
|
|
203
|
+
cy.log(`Created {singular} with defaults: ${{response.body.data.id}}`)
|
|
204
|
+
}})
|
|
205
|
+
}})
|
|
206
|
+
|
|
207
|
+
it('{entity_upper}_API_012: Should reject creation without x-team-id', () => {{
|
|
208
|
+
const originalTeamId = api.teamId
|
|
209
|
+
api.setTeamId(null)
|
|
210
|
+
|
|
211
|
+
const data = api.generateRandom{pascal_singular}Data()
|
|
212
|
+
|
|
213
|
+
api.create{pascal_singular}(data).then((response: any) => {{
|
|
214
|
+
expect(response.status).to.eq(400)
|
|
215
|
+
expect(response.body.success).to.be.false
|
|
216
|
+
expect(response.body.code).to.eq('TEAM_CONTEXT_REQUIRED')
|
|
217
|
+
}})
|
|
218
|
+
|
|
219
|
+
api.setTeamId(originalTeamId)
|
|
220
|
+
}})
|
|
221
|
+
}})
|
|
222
|
+
|
|
223
|
+
// ============================================================
|
|
224
|
+
// GET /api/v1/{entity}/{{id}} - Get {pascal_singular} by ID
|
|
225
|
+
// ============================================================
|
|
226
|
+
describe('GET /api/v1/{entity}/{{id}} - Get {pascal_singular} by ID', () => {{
|
|
227
|
+
let testEntity: any
|
|
228
|
+
|
|
229
|
+
beforeEach(() => {{
|
|
230
|
+
const data = api.generateRandom{pascal_singular}Data()
|
|
231
|
+
|
|
232
|
+
api.create{pascal_singular}(data).then((response: any) => {{
|
|
233
|
+
testEntity = response.body.data
|
|
234
|
+
createdEntities.push(testEntity)
|
|
235
|
+
}})
|
|
236
|
+
}})
|
|
237
|
+
|
|
238
|
+
it('{entity_upper}_API_020: Should get {singular} by valid ID', () => {{
|
|
239
|
+
cy.then(() => {{
|
|
240
|
+
api.get{pascal_singular}ById(testEntity.id).then((response: any) => {{
|
|
241
|
+
api.validateSuccessResponse(response, 200)
|
|
242
|
+
api.validate{pascal_singular}Object(response.body.data)
|
|
243
|
+
|
|
244
|
+
expect(response.body.data.id).to.eq(testEntity.id)
|
|
245
|
+
|
|
246
|
+
cy.log(`Got {singular} by ID: ${{testEntity.id}}`)
|
|
247
|
+
}})
|
|
248
|
+
}})
|
|
249
|
+
}})
|
|
250
|
+
|
|
251
|
+
it('{entity_upper}_API_021: Should return 404 for non-existent {singular}', () => {{
|
|
252
|
+
const nonExistentId = 'non-existent-id-12345'
|
|
253
|
+
|
|
254
|
+
api.get{pascal_singular}ById(nonExistentId).then((response: any) => {{
|
|
255
|
+
expect(response.status).to.eq(404)
|
|
256
|
+
expect(response.body.success).to.be.false
|
|
257
|
+
}})
|
|
258
|
+
}})
|
|
259
|
+
}})
|
|
260
|
+
|
|
261
|
+
// ============================================================
|
|
262
|
+
// PATCH /api/v1/{entity}/{{id}} - Update {pascal_singular}
|
|
263
|
+
// ============================================================
|
|
264
|
+
describe('PATCH /api/v1/{entity}/{{id}} - Update {pascal_singular}', () => {{
|
|
265
|
+
let testEntity: any
|
|
266
|
+
|
|
267
|
+
beforeEach(() => {{
|
|
268
|
+
const data = api.generateRandom{pascal_singular}Data()
|
|
269
|
+
|
|
270
|
+
api.create{pascal_singular}(data).then((response: any) => {{
|
|
271
|
+
testEntity = response.body.data
|
|
272
|
+
createdEntities.push(testEntity)
|
|
273
|
+
}})
|
|
274
|
+
}})
|
|
275
|
+
|
|
276
|
+
it('{entity_upper}_API_030: Should update {singular} with valid data', () => {{
|
|
277
|
+
// TODO: Customize update data based on entity schema
|
|
278
|
+
const updateData = {{
|
|
279
|
+
// title: 'Updated Title'
|
|
280
|
+
}}
|
|
281
|
+
|
|
282
|
+
cy.then(() => {{
|
|
283
|
+
api.update{pascal_singular}(testEntity.id, updateData).then((response: any) => {{
|
|
284
|
+
api.validateSuccessResponse(response, 200)
|
|
285
|
+
api.validate{pascal_singular}Object(response.body.data)
|
|
286
|
+
|
|
287
|
+
// TODO: Add entity-specific validations
|
|
288
|
+
// expect(response.body.data.title).to.eq(updateData.title)
|
|
289
|
+
expect(response.body.data.id).to.eq(testEntity.id)
|
|
290
|
+
|
|
291
|
+
cy.log(`Updated {singular}: ${{testEntity.id}}`)
|
|
292
|
+
}})
|
|
293
|
+
}})
|
|
294
|
+
}})
|
|
295
|
+
|
|
296
|
+
it('{entity_upper}_API_031: Should return 404 for non-existent {singular}', () => {{
|
|
297
|
+
const nonExistentId = 'non-existent-id-12345'
|
|
298
|
+
const updateData = {{ /* minimal update */ }}
|
|
299
|
+
|
|
300
|
+
api.update{pascal_singular}(nonExistentId, updateData).then((response: any) => {{
|
|
301
|
+
expect(response.status).to.eq(404)
|
|
302
|
+
expect(response.body.success).to.be.false
|
|
303
|
+
}})
|
|
304
|
+
}})
|
|
305
|
+
|
|
306
|
+
it('{entity_upper}_API_032: Should reject empty update body', () => {{
|
|
307
|
+
cy.then(() => {{
|
|
308
|
+
api.update{pascal_singular}(testEntity.id, {{}}).then((response: any) => {{
|
|
309
|
+
expect(response.status).to.eq(400)
|
|
310
|
+
expect(response.body.success).to.be.false
|
|
311
|
+
}})
|
|
312
|
+
}})
|
|
313
|
+
}})
|
|
314
|
+
}})
|
|
315
|
+
|
|
316
|
+
// ============================================================
|
|
317
|
+
// DELETE /api/v1/{entity}/{{id}} - Delete {pascal_singular}
|
|
318
|
+
// ============================================================
|
|
319
|
+
describe('DELETE /api/v1/{entity}/{{id}} - Delete {pascal_singular}', () => {{
|
|
320
|
+
let testEntity: any
|
|
321
|
+
|
|
322
|
+
beforeEach(() => {{
|
|
323
|
+
const data = api.generateRandom{pascal_singular}Data()
|
|
324
|
+
|
|
325
|
+
api.create{pascal_singular}(data).then((response: any) => {{
|
|
326
|
+
testEntity = response.body.data
|
|
327
|
+
// Don't add to createdEntities - we'll delete manually
|
|
328
|
+
}})
|
|
329
|
+
}})
|
|
330
|
+
|
|
331
|
+
it('{entity_upper}_API_040: Should delete {singular} by valid ID', () => {{
|
|
332
|
+
cy.then(() => {{
|
|
333
|
+
api.delete{pascal_singular}(testEntity.id).then((response: any) => {{
|
|
334
|
+
api.validateSuccessResponse(response, 200)
|
|
335
|
+
expect(response.body.data.success).to.be.true
|
|
336
|
+
expect(response.body.data.id).to.exist
|
|
337
|
+
|
|
338
|
+
cy.log(`Deleted {singular}: ${{testEntity.id}}`)
|
|
339
|
+
|
|
340
|
+
// Verify deletion
|
|
341
|
+
api.get{pascal_singular}ById(testEntity.id).then((getResponse: any) => {{
|
|
342
|
+
expect(getResponse.status).to.eq(404)
|
|
343
|
+
}})
|
|
344
|
+
}})
|
|
345
|
+
}})
|
|
346
|
+
}})
|
|
347
|
+
|
|
348
|
+
it('{entity_upper}_API_041: Should return 404 for non-existent {singular}', () => {{
|
|
349
|
+
const nonExistentId = 'non-existent-id-12345'
|
|
350
|
+
|
|
351
|
+
api.delete{pascal_singular}(nonExistentId).then((response: any) => {{
|
|
352
|
+
expect(response.status).to.eq(404)
|
|
353
|
+
expect(response.body.success).to.be.false
|
|
354
|
+
}})
|
|
355
|
+
|
|
356
|
+
// Add testEntity to cleanup since we didn't delete it
|
|
357
|
+
createdEntities.push(testEntity)
|
|
358
|
+
}})
|
|
359
|
+
}})
|
|
360
|
+
|
|
361
|
+
// ============================================================
|
|
362
|
+
// Integration Test - Complete CRUD Lifecycle
|
|
363
|
+
// ============================================================
|
|
364
|
+
describe('Integration - Complete CRUD Lifecycle', () => {{
|
|
365
|
+
it('{entity_upper}_API_100: Should complete full lifecycle: Create -> Read -> Update -> Delete', () => {{
|
|
366
|
+
// TODO: Customize test data
|
|
367
|
+
const createData = api.generateRandom{pascal_singular}Data()
|
|
368
|
+
|
|
369
|
+
// 1. CREATE
|
|
370
|
+
api.create{pascal_singular}(createData).then((createResponse: any) => {{
|
|
371
|
+
api.validateSuccessResponse(createResponse, 201)
|
|
372
|
+
const created = createResponse.body.data
|
|
373
|
+
cy.log(`1. Created: ${{created.id}}`)
|
|
374
|
+
|
|
375
|
+
// 2. READ
|
|
376
|
+
api.get{pascal_singular}ById(created.id).then((readResponse: any) => {{
|
|
377
|
+
api.validateSuccessResponse(readResponse, 200)
|
|
378
|
+
expect(readResponse.body.data.id).to.eq(created.id)
|
|
379
|
+
cy.log(`2. Read: ${{created.id}}`)
|
|
380
|
+
|
|
381
|
+
// 3. UPDATE
|
|
382
|
+
const updateData = {{
|
|
383
|
+
// TODO: Add update fields
|
|
384
|
+
}}
|
|
385
|
+
api.update{pascal_singular}(created.id, updateData).then((updateResponse: any) => {{
|
|
386
|
+
api.validateSuccessResponse(updateResponse, 200)
|
|
387
|
+
cy.log(`3. Updated: ${{created.id}}`)
|
|
388
|
+
|
|
389
|
+
// 4. DELETE
|
|
390
|
+
api.delete{pascal_singular}(created.id).then((deleteResponse: any) => {{
|
|
391
|
+
api.validateSuccessResponse(deleteResponse, 200)
|
|
392
|
+
expect(deleteResponse.body.data.success).to.be.true
|
|
393
|
+
cy.log(`4. Deleted: ${{created.id}}`)
|
|
394
|
+
|
|
395
|
+
// 5. VERIFY DELETION
|
|
396
|
+
api.get{pascal_singular}ById(created.id).then((finalResponse: any) => {{
|
|
397
|
+
expect(finalResponse.status).to.eq(404)
|
|
398
|
+
cy.log(`5. Verified deletion: 404`)
|
|
399
|
+
cy.log('Full CRUD lifecycle completed successfully')
|
|
400
|
+
}})
|
|
401
|
+
}})
|
|
402
|
+
}})
|
|
403
|
+
}})
|
|
404
|
+
}})
|
|
405
|
+
}})
|
|
406
|
+
}})
|
|
407
|
+
}})
|
|
408
|
+
'''
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def generate_bdd_content(entity: str, theme: str) -> str:
|
|
412
|
+
"""Generate the BDD documentation file content."""
|
|
413
|
+
singular = to_singular(entity)
|
|
414
|
+
pascal_singular = to_pascal_case(singular)
|
|
415
|
+
pascal_plural = to_pascal_case(entity)
|
|
416
|
+
entity_upper = entity.upper()
|
|
417
|
+
|
|
418
|
+
return f'''---
|
|
419
|
+
feature: {pascal_plural} API
|
|
420
|
+
priority: high
|
|
421
|
+
tags: [api, feat-{entity}, crud, regression]
|
|
422
|
+
grepTags: ["@api", "@feat-{entity}"]
|
|
423
|
+
coverage: 14 tests
|
|
424
|
+
---
|
|
425
|
+
|
|
426
|
+
# {pascal_plural} API
|
|
427
|
+
|
|
428
|
+
> API tests for /api/v1/{entity} CRUD endpoints. Uses superadmin API key with team context.
|
|
429
|
+
|
|
430
|
+
## Endpoints Covered
|
|
431
|
+
|
|
432
|
+
| Endpoint | Method | Description |
|
|
433
|
+
|----------|--------|-------------|
|
|
434
|
+
| `/api/v1/{entity}` | GET | List {entity} (paginated) |
|
|
435
|
+
| `/api/v1/{entity}` | POST | Create new {singular} |
|
|
436
|
+
| `/api/v1/{entity}/{{id}}` | GET | Get {singular} by ID |
|
|
437
|
+
| `/api/v1/{entity}/{{id}}` | PATCH | Update {singular} |
|
|
438
|
+
| `/api/v1/{entity}/{{id}}` | DELETE | Delete {singular} |
|
|
439
|
+
|
|
440
|
+
---
|
|
441
|
+
|
|
442
|
+
## @test {entity_upper}_API_001: List {entity} with valid API key
|
|
443
|
+
|
|
444
|
+
### Metadata
|
|
445
|
+
- **Priority:** Critical
|
|
446
|
+
- **Type:** Smoke
|
|
447
|
+
- **Tags:** api, {entity}, list, authentication
|
|
448
|
+
- **Grep:** `@smoke @feat-{entity}`
|
|
449
|
+
|
|
450
|
+
```gherkin:en
|
|
451
|
+
Scenario: List {entity} with valid authentication
|
|
452
|
+
|
|
453
|
+
Given I have a valid superadmin API key
|
|
454
|
+
And I have x-team-id header set to a valid team
|
|
455
|
+
When I make a GET request to /api/v1/{entity}
|
|
456
|
+
Then the response status should be 200
|
|
457
|
+
And the response body should have success true
|
|
458
|
+
And the data should be an array of {entity}
|
|
459
|
+
And the info should contain total, page, and limit
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
```gherkin:es
|
|
463
|
+
Scenario: Listar {entity} con autenticacion valida
|
|
464
|
+
|
|
465
|
+
Given tengo una API key de superadmin valida
|
|
466
|
+
And tengo el header x-team-id configurado con un team valido
|
|
467
|
+
When hago una solicitud GET a /api/v1/{entity}
|
|
468
|
+
Then el status de respuesta deberia ser 200
|
|
469
|
+
And el body deberia tener success true
|
|
470
|
+
And los datos deberian ser un array de {entity}
|
|
471
|
+
And info deberia contener total, page y limit
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
---
|
|
475
|
+
|
|
476
|
+
## @test {entity_upper}_API_002: List {entity} with pagination
|
|
477
|
+
|
|
478
|
+
### Metadata
|
|
479
|
+
- **Priority:** Normal
|
|
480
|
+
- **Type:** Functional
|
|
481
|
+
- **Tags:** api, {entity}, pagination
|
|
482
|
+
|
|
483
|
+
```gherkin:en
|
|
484
|
+
Scenario: List {entity} with pagination parameters
|
|
485
|
+
|
|
486
|
+
Given I have valid authentication
|
|
487
|
+
When I make a GET request to /api/v1/{entity}?page=1&limit=5
|
|
488
|
+
Then the response status should be 200
|
|
489
|
+
And info.page should be 1
|
|
490
|
+
And info.limit should be 5
|
|
491
|
+
And data array length should be at most 5
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
```gherkin:es
|
|
495
|
+
Scenario: Listar {entity} con parametros de paginacion
|
|
496
|
+
|
|
497
|
+
Given tengo autenticacion valida
|
|
498
|
+
When hago una solicitud GET a /api/v1/{entity}?page=1&limit=5
|
|
499
|
+
Then el status de respuesta deberia ser 200
|
|
500
|
+
And info.page deberia ser 1
|
|
501
|
+
And info.limit deberia ser 5
|
|
502
|
+
And la longitud del array data deberia ser maximo 5
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
---
|
|
506
|
+
|
|
507
|
+
## @test {entity_upper}_API_003: Reject request without API key
|
|
508
|
+
|
|
509
|
+
### Metadata
|
|
510
|
+
- **Priority:** Critical
|
|
511
|
+
- **Type:** Security
|
|
512
|
+
- **Tags:** api, {entity}, authentication, 401
|
|
513
|
+
|
|
514
|
+
```gherkin:en
|
|
515
|
+
Scenario: Request without API key returns 401
|
|
516
|
+
|
|
517
|
+
Given I make a request without Authorization header
|
|
518
|
+
When I make a GET request to /api/v1/{entity}
|
|
519
|
+
Then the response status should be 401
|
|
520
|
+
And the response body should have success false
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
```gherkin:es
|
|
524
|
+
Scenario: Solicitud sin API key retorna 401
|
|
525
|
+
|
|
526
|
+
Given hago una solicitud sin header Authorization
|
|
527
|
+
When hago una solicitud GET a /api/v1/{entity}
|
|
528
|
+
Then el status de respuesta deberia ser 401
|
|
529
|
+
And el body deberia tener success false
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
---
|
|
533
|
+
|
|
534
|
+
## @test {entity_upper}_API_004: Reject request without x-team-id
|
|
535
|
+
|
|
536
|
+
### Metadata
|
|
537
|
+
- **Priority:** Critical
|
|
538
|
+
- **Type:** Security
|
|
539
|
+
- **Tags:** api, {entity}, team-context, 400
|
|
540
|
+
|
|
541
|
+
```gherkin:en
|
|
542
|
+
Scenario: Request without x-team-id returns 400
|
|
543
|
+
|
|
544
|
+
Given I have a valid API key
|
|
545
|
+
But I do not include x-team-id header
|
|
546
|
+
When I make a GET request to /api/v1/{entity}
|
|
547
|
+
Then the response status should be 400
|
|
548
|
+
And the error code should be TEAM_CONTEXT_REQUIRED
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
```gherkin:es
|
|
552
|
+
Scenario: Solicitud sin x-team-id retorna 400
|
|
553
|
+
|
|
554
|
+
Given tengo una API key valida
|
|
555
|
+
But no incluyo el header x-team-id
|
|
556
|
+
When hago una solicitud GET a /api/v1/{entity}
|
|
557
|
+
Then el status de respuesta deberia ser 400
|
|
558
|
+
And el codigo de error deberia ser TEAM_CONTEXT_REQUIRED
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
---
|
|
562
|
+
|
|
563
|
+
## @test {entity_upper}_API_010: Create {singular} with valid data
|
|
564
|
+
|
|
565
|
+
### Metadata
|
|
566
|
+
- **Priority:** Critical
|
|
567
|
+
- **Type:** Smoke
|
|
568
|
+
- **Tags:** api, {entity}, create, 201
|
|
569
|
+
|
|
570
|
+
```gherkin:en
|
|
571
|
+
Scenario: Create new {singular} successfully
|
|
572
|
+
|
|
573
|
+
Given I have valid authentication and team context
|
|
574
|
+
And I have valid {singular} data
|
|
575
|
+
When I make a POST request to /api/v1/{entity}
|
|
576
|
+
Then the response status should be 201
|
|
577
|
+
And the response body should have success true
|
|
578
|
+
And the data should contain the created {singular} with an ID
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
```gherkin:es
|
|
582
|
+
Scenario: Crear nuevo/a {singular} exitosamente
|
|
583
|
+
|
|
584
|
+
Given tengo autenticacion y contexto de team validos
|
|
585
|
+
And tengo datos de {singular} validos
|
|
586
|
+
When hago una solicitud POST a /api/v1/{entity}
|
|
587
|
+
Then el status de respuesta deberia ser 201
|
|
588
|
+
And el body deberia tener success true
|
|
589
|
+
And los datos deberian contener el/la {singular} creado/a con un ID
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
---
|
|
593
|
+
|
|
594
|
+
## @test {entity_upper}_API_020: Get {singular} by valid ID
|
|
595
|
+
|
|
596
|
+
### Metadata
|
|
597
|
+
- **Priority:** Normal
|
|
598
|
+
- **Type:** Functional
|
|
599
|
+
- **Tags:** api, {entity}, read
|
|
600
|
+
|
|
601
|
+
```gherkin:en
|
|
602
|
+
Scenario: Get {singular} by valid ID
|
|
603
|
+
|
|
604
|
+
Given I have valid authentication
|
|
605
|
+
And a {singular} exists with a known ID
|
|
606
|
+
When I make a GET request to /api/v1/{entity}/{{id}}
|
|
607
|
+
Then the response status should be 200
|
|
608
|
+
And the data should contain the {singular} details
|
|
609
|
+
And data.id should match the requested ID
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
```gherkin:es
|
|
613
|
+
Scenario: Obtener {singular} por ID valido
|
|
614
|
+
|
|
615
|
+
Given tengo autenticacion valida
|
|
616
|
+
And existe un/a {singular} con un ID conocido
|
|
617
|
+
When hago una solicitud GET a /api/v1/{entity}/{{id}}
|
|
618
|
+
Then el status de respuesta deberia ser 200
|
|
619
|
+
And los datos deberian contener los detalles del/la {singular}
|
|
620
|
+
And data.id deberia coincidir con el ID solicitado
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
---
|
|
624
|
+
|
|
625
|
+
## @test {entity_upper}_API_021: Return 404 for non-existent {singular}
|
|
626
|
+
|
|
627
|
+
### Metadata
|
|
628
|
+
- **Priority:** Normal
|
|
629
|
+
- **Type:** Functional
|
|
630
|
+
- **Tags:** api, {entity}, 404
|
|
631
|
+
|
|
632
|
+
```gherkin:en
|
|
633
|
+
Scenario: Get non-existent {singular} returns 404
|
|
634
|
+
|
|
635
|
+
Given I have valid authentication
|
|
636
|
+
When I make a GET request to /api/v1/{entity}/non-existent-id
|
|
637
|
+
Then the response status should be 404
|
|
638
|
+
And the response body should have success false
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
```gherkin:es
|
|
642
|
+
Scenario: Obtener {singular} inexistente retorna 404
|
|
643
|
+
|
|
644
|
+
Given tengo autenticacion valida
|
|
645
|
+
When hago una solicitud GET a /api/v1/{entity}/id-inexistente
|
|
646
|
+
Then el status de respuesta deberia ser 404
|
|
647
|
+
And el body deberia tener success false
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
---
|
|
651
|
+
|
|
652
|
+
## @test {entity_upper}_API_030: Update {singular} with valid data
|
|
653
|
+
|
|
654
|
+
### Metadata
|
|
655
|
+
- **Priority:** Normal
|
|
656
|
+
- **Type:** Functional
|
|
657
|
+
- **Tags:** api, {entity}, update
|
|
658
|
+
|
|
659
|
+
```gherkin:en
|
|
660
|
+
Scenario: Update {singular} successfully
|
|
661
|
+
|
|
662
|
+
Given I have valid authentication
|
|
663
|
+
And a {singular} exists with a known ID
|
|
664
|
+
And I have valid update data
|
|
665
|
+
When I make a PATCH request to /api/v1/{entity}/{{id}}
|
|
666
|
+
Then the response status should be 200
|
|
667
|
+
And the data should contain the updated {singular}
|
|
668
|
+
And the updated fields should reflect my changes
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
```gherkin:es
|
|
672
|
+
Scenario: Actualizar {singular} exitosamente
|
|
673
|
+
|
|
674
|
+
Given tengo autenticacion valida
|
|
675
|
+
And existe un/a {singular} con un ID conocido
|
|
676
|
+
And tengo datos de actualizacion validos
|
|
677
|
+
When hago una solicitud PATCH a /api/v1/{entity}/{{id}}
|
|
678
|
+
Then el status de respuesta deberia ser 200
|
|
679
|
+
And los datos deberian contener el/la {singular} actualizado/a
|
|
680
|
+
And los campos actualizados deberian reflejar mis cambios
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
---
|
|
684
|
+
|
|
685
|
+
## @test {entity_upper}_API_040: Delete {singular} by valid ID
|
|
686
|
+
|
|
687
|
+
### Metadata
|
|
688
|
+
- **Priority:** Normal
|
|
689
|
+
- **Type:** Functional
|
|
690
|
+
- **Tags:** api, {entity}, delete
|
|
691
|
+
|
|
692
|
+
```gherkin:en
|
|
693
|
+
Scenario: Delete {singular} successfully
|
|
694
|
+
|
|
695
|
+
Given I have valid authentication
|
|
696
|
+
And a {singular} exists with a known ID
|
|
697
|
+
When I make a DELETE request to /api/v1/{entity}/{{id}}
|
|
698
|
+
Then the response status should be 200
|
|
699
|
+
And data.success should be true
|
|
700
|
+
And subsequent GET for the same ID should return 404
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
```gherkin:es
|
|
704
|
+
Scenario: Eliminar {singular} exitosamente
|
|
705
|
+
|
|
706
|
+
Given tengo autenticacion valida
|
|
707
|
+
And existe un/a {singular} con un ID conocido
|
|
708
|
+
When hago una solicitud DELETE a /api/v1/{entity}/{{id}}
|
|
709
|
+
Then el status de respuesta deberia ser 200
|
|
710
|
+
And data.success deberia ser true
|
|
711
|
+
And un GET posterior para el mismo ID deberia retornar 404
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
---
|
|
715
|
+
|
|
716
|
+
## @test {entity_upper}_API_100: Complete CRUD Lifecycle
|
|
717
|
+
|
|
718
|
+
### Metadata
|
|
719
|
+
- **Priority:** Critical
|
|
720
|
+
- **Type:** Integration
|
|
721
|
+
- **Tags:** api, {entity}, integration, lifecycle
|
|
722
|
+
|
|
723
|
+
```gherkin:en
|
|
724
|
+
Scenario: Complete CRUD lifecycle (Create -> Read -> Update -> Delete)
|
|
725
|
+
|
|
726
|
+
Given I have valid authentication and team context
|
|
727
|
+
When I create a new {singular}
|
|
728
|
+
Then the {singular} should be created with status 201
|
|
729
|
+
|
|
730
|
+
When I read the created {singular} by ID
|
|
731
|
+
Then I should get the {singular} details with status 200
|
|
732
|
+
|
|
733
|
+
When I update the {singular} with new data
|
|
734
|
+
Then the {singular} should be updated with status 200
|
|
735
|
+
|
|
736
|
+
When I delete the {singular}
|
|
737
|
+
Then the {singular} should be deleted with status 200
|
|
738
|
+
|
|
739
|
+
When I try to read the deleted {singular}
|
|
740
|
+
Then I should get a 404 response
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
```gherkin:es
|
|
744
|
+
Scenario: Ciclo de vida CRUD completo (Crear -> Leer -> Actualizar -> Eliminar)
|
|
745
|
+
|
|
746
|
+
Given tengo autenticacion y contexto de team validos
|
|
747
|
+
When creo un/a nuevo/a {singular}
|
|
748
|
+
Then el/la {singular} deberia crearse con status 201
|
|
749
|
+
|
|
750
|
+
When leo el/la {singular} creado/a por ID
|
|
751
|
+
Then deberia obtener los detalles con status 200
|
|
752
|
+
|
|
753
|
+
When actualizo el/la {singular} con nuevos datos
|
|
754
|
+
Then el/la {singular} deberia actualizarse con status 200
|
|
755
|
+
|
|
756
|
+
When elimino el/la {singular}
|
|
757
|
+
Then el/la {singular} deberia eliminarse con status 200
|
|
758
|
+
|
|
759
|
+
When intento leer el/la {singular} eliminado/a
|
|
760
|
+
Then deberia obtener una respuesta 404
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
---
|
|
764
|
+
|
|
765
|
+
## Response Formats
|
|
766
|
+
|
|
767
|
+
### Success Response (200/201)
|
|
768
|
+
|
|
769
|
+
```json
|
|
770
|
+
{{
|
|
771
|
+
"success": true,
|
|
772
|
+
"data": {{ ... }},
|
|
773
|
+
"info": {{
|
|
774
|
+
"total": 10,
|
|
775
|
+
"page": 1,
|
|
776
|
+
"limit": 10
|
|
777
|
+
}}
|
|
778
|
+
}}
|
|
779
|
+
```
|
|
780
|
+
|
|
781
|
+
### Error Response (400 - TEAM_CONTEXT_REQUIRED)
|
|
782
|
+
|
|
783
|
+
```json
|
|
784
|
+
{{
|
|
785
|
+
"success": false,
|
|
786
|
+
"error": {{
|
|
787
|
+
"message": "Team context required",
|
|
788
|
+
"code": "TEAM_CONTEXT_REQUIRED",
|
|
789
|
+
"details": {{
|
|
790
|
+
"hint": "Include x-team-id header with valid team ID"
|
|
791
|
+
}}
|
|
792
|
+
}}
|
|
793
|
+
}}
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
### Error Response (401 - Unauthorized)
|
|
797
|
+
|
|
798
|
+
```json
|
|
799
|
+
{{
|
|
800
|
+
"success": false,
|
|
801
|
+
"error": {{
|
|
802
|
+
"message": "Authentication required",
|
|
803
|
+
"code": "AUTHENTICATION_REQUIRED",
|
|
804
|
+
"details": {{
|
|
805
|
+
"hint": "Provide valid API key via Authorization or x-api-key header"
|
|
806
|
+
}}
|
|
807
|
+
}}
|
|
808
|
+
}}
|
|
809
|
+
```
|
|
810
|
+
|
|
811
|
+
### Error Response (404 - Not Found)
|
|
812
|
+
|
|
813
|
+
```json
|
|
814
|
+
{{
|
|
815
|
+
"success": false,
|
|
816
|
+
"error": {{
|
|
817
|
+
"message": "{pascal_singular} not found",
|
|
818
|
+
"code": "NOT_FOUND"
|
|
819
|
+
}}
|
|
820
|
+
}}
|
|
821
|
+
```
|
|
822
|
+
|
|
823
|
+
---
|
|
824
|
+
|
|
825
|
+
## Test Summary
|
|
826
|
+
|
|
827
|
+
| Test ID | Endpoint | Method | Description | Tags |
|
|
828
|
+
|---------|----------|--------|-------------|------|
|
|
829
|
+
| {entity_upper}_API_001 | /{entity} | GET | List with valid auth | `@smoke` |
|
|
830
|
+
| {entity_upper}_API_002 | /{entity} | GET | List with pagination | |
|
|
831
|
+
| {entity_upper}_API_003 | /{entity} | GET | 401 without API key | |
|
|
832
|
+
| {entity_upper}_API_004 | /{entity} | GET | 400 without x-team-id | |
|
|
833
|
+
| {entity_upper}_API_010 | /{entity} | POST | Create with valid data | `@smoke` |
|
|
834
|
+
| {entity_upper}_API_011 | /{entity} | POST | Create with minimal data | |
|
|
835
|
+
| {entity_upper}_API_012 | /{entity} | POST | 400 without x-team-id | |
|
|
836
|
+
| {entity_upper}_API_020 | /{entity}/{{id}} | GET | Get by valid ID | |
|
|
837
|
+
| {entity_upper}_API_021 | /{entity}/{{id}} | GET | 404 non-existent | |
|
|
838
|
+
| {entity_upper}_API_030 | /{entity}/{{id}} | PATCH | Update with valid data | |
|
|
839
|
+
| {entity_upper}_API_031 | /{entity}/{{id}} | PATCH | 404 non-existent | |
|
|
840
|
+
| {entity_upper}_API_032 | /{entity}/{{id}} | PATCH | 400 empty body | |
|
|
841
|
+
| {entity_upper}_API_040 | /{entity}/{{id}} | DELETE | Delete by valid ID | |
|
|
842
|
+
| {entity_upper}_API_041 | /{entity}/{{id}} | DELETE | 404 non-existent | |
|
|
843
|
+
| {entity_upper}_API_100 | All | All | Full CRUD lifecycle | |
|
|
844
|
+
'''
|
|
845
|
+
|
|
846
|
+
|
|
847
|
+
def main():
|
|
848
|
+
parser = argparse.ArgumentParser(description='Generate API test file')
|
|
849
|
+
parser.add_argument('--entity', required=True, help='Entity name (e.g., tasks)')
|
|
850
|
+
parser.add_argument('--theme', default='default', help='Theme name')
|
|
851
|
+
parser.add_argument('--session', default=None, help='Session name for @scope tag')
|
|
852
|
+
parser.add_argument('--with-bdd', action='store_true', help='Generate BDD documentation')
|
|
853
|
+
parser.add_argument('--dry-run', action='store_true', help='Preview without writing')
|
|
854
|
+
parser.add_argument('--output', default=None, help='Output directory path')
|
|
855
|
+
|
|
856
|
+
args = parser.parse_args()
|
|
857
|
+
|
|
858
|
+
entity = args.entity.lower()
|
|
859
|
+
theme = args.theme
|
|
860
|
+
|
|
861
|
+
print(f"\n{'=' * 60}")
|
|
862
|
+
print("GENERATING API TEST")
|
|
863
|
+
print(f"{'=' * 60}")
|
|
864
|
+
print(f"Entity: {entity}")
|
|
865
|
+
print(f"Theme: {theme}")
|
|
866
|
+
print(f"Session: {args.session or '(none)'}")
|
|
867
|
+
print(f"With BDD: {args.with_bdd}")
|
|
868
|
+
print(f"{'=' * 60}\n")
|
|
869
|
+
|
|
870
|
+
# Generate test content
|
|
871
|
+
test_content = generate_test_content(entity, theme, args.session)
|
|
872
|
+
|
|
873
|
+
# Determine output paths
|
|
874
|
+
if args.output:
|
|
875
|
+
output_dir = Path(args.output)
|
|
876
|
+
else:
|
|
877
|
+
output_dir = Path(f'contents/themes/{theme}/tests/cypress/e2e/api/entities')
|
|
878
|
+
|
|
879
|
+
test_file = output_dir / f'{entity}-crud.cy.ts'
|
|
880
|
+
bdd_file = output_dir / f'{entity}-crud.bdd.md'
|
|
881
|
+
|
|
882
|
+
if args.dry_run:
|
|
883
|
+
print("DRY RUN - Generated test content:\n")
|
|
884
|
+
print("-" * 60)
|
|
885
|
+
print(test_content[:2000] + "\n... (truncated)")
|
|
886
|
+
print("-" * 60)
|
|
887
|
+
|
|
888
|
+
if args.with_bdd:
|
|
889
|
+
bdd_content = generate_bdd_content(entity, theme)
|
|
890
|
+
print("\nDRY RUN - Generated BDD content:\n")
|
|
891
|
+
print("-" * 60)
|
|
892
|
+
print(bdd_content[:2000] + "\n... (truncated)")
|
|
893
|
+
print("-" * 60)
|
|
894
|
+
|
|
895
|
+
print("\nRun without --dry-run to write files.")
|
|
896
|
+
return 0
|
|
897
|
+
|
|
898
|
+
# Create directories
|
|
899
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
900
|
+
|
|
901
|
+
# Write test file
|
|
902
|
+
with open(test_file, 'w', encoding='utf-8') as f:
|
|
903
|
+
f.write(test_content)
|
|
904
|
+
print(f"Test file generated: {test_file}")
|
|
905
|
+
|
|
906
|
+
# Write BDD file if requested
|
|
907
|
+
if args.with_bdd:
|
|
908
|
+
bdd_content = generate_bdd_content(entity, theme)
|
|
909
|
+
with open(bdd_file, 'w', encoding='utf-8') as f:
|
|
910
|
+
f.write(bdd_content)
|
|
911
|
+
print(f"BDD file generated: {bdd_file}")
|
|
912
|
+
|
|
913
|
+
pascal_plural = to_pascal_case(entity)
|
|
914
|
+
pascal_singular = to_pascal_case(to_singular(entity))
|
|
915
|
+
|
|
916
|
+
print(f"\n{'=' * 60}")
|
|
917
|
+
print("NEXT STEPS:")
|
|
918
|
+
print("=" * 60)
|
|
919
|
+
print(f"1. Review generated file(s)")
|
|
920
|
+
print(f"2. Ensure {pascal_plural}APIController exists in src/controllers/")
|
|
921
|
+
print(f"3. Customize generateRandom{pascal_singular}Data() with entity fields")
|
|
922
|
+
print(f"4. Customize TODO sections in test file")
|
|
923
|
+
print(f"5. Run tests: pnpm cy:run --spec \"{test_file}\"")
|
|
924
|
+
print("=" * 60 + "\n")
|
|
925
|
+
|
|
926
|
+
return 0
|
|
927
|
+
|
|
928
|
+
|
|
929
|
+
if __name__ == '__main__':
|
|
930
|
+
sys.exit(main())
|