@codyswann/lisa 1.0.0
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 +867 -0
- package/all/copy-overwrite/.claude/README.md +205 -0
- package/all/copy-overwrite/.claude/agents/agent-architect.md +311 -0
- package/all/copy-overwrite/.claude/agents/codebase-analyzer.md +146 -0
- package/all/copy-overwrite/.claude/agents/codebase-locator.md +125 -0
- package/all/copy-overwrite/.claude/agents/codebase-pattern-finder.md +237 -0
- package/all/copy-overwrite/.claude/agents/git-history-analyzer.md +183 -0
- package/all/copy-overwrite/.claude/agents/hooks-expert.md +74 -0
- package/all/copy-overwrite/.claude/agents/skill-evaluator.md +246 -0
- package/all/copy-overwrite/.claude/agents/slash-command-architect.md +87 -0
- package/all/copy-overwrite/.claude/agents/web-search-researcher.md +112 -0
- package/all/copy-overwrite/.claude/commands/git/commit-and-submit-pr.md +8 -0
- package/all/copy-overwrite/.claude/commands/git/commit.md +44 -0
- package/all/copy-overwrite/.claude/commands/git/prune.md +34 -0
- package/all/copy-overwrite/.claude/commands/git/submit-pr.md +50 -0
- package/all/copy-overwrite/.claude/commands/jira/create.md +50 -0
- package/all/copy-overwrite/.claude/commands/jira/verify.md +34 -0
- package/all/copy-overwrite/.claude/commands/project/archive.md +8 -0
- package/all/copy-overwrite/.claude/commands/project/bootstrap.md +49 -0
- package/all/copy-overwrite/.claude/commands/project/complete-task.md +7 -0
- package/all/copy-overwrite/.claude/commands/project/debrief.md +65 -0
- package/all/copy-overwrite/.claude/commands/project/execute.md +94 -0
- package/all/copy-overwrite/.claude/commands/project/implement.md +42 -0
- package/all/copy-overwrite/.claude/commands/project/local-code-review.md +88 -0
- package/all/copy-overwrite/.claude/commands/project/lower-code-complexity.md +74 -0
- package/all/copy-overwrite/.claude/commands/project/plan.md +314 -0
- package/all/copy-overwrite/.claude/commands/project/research.md +248 -0
- package/all/copy-overwrite/.claude/commands/project/review.md +63 -0
- package/all/copy-overwrite/.claude/commands/project/setup.md +19 -0
- package/all/copy-overwrite/.claude/commands/project/verify.md +38 -0
- package/all/copy-overwrite/.claude/commands/pull-request/review.md +12 -0
- package/all/copy-overwrite/.claude/commands/rules/format-md.md +72 -0
- package/all/copy-overwrite/.claude/commands/sonarqube/check.md +6 -0
- package/all/copy-overwrite/.claude/commands/sonarqube/fix.md +3 -0
- package/all/copy-overwrite/.claude/hooks/README.md +301 -0
- package/all/copy-overwrite/.claude/hooks/notify-ntfy.sh +181 -0
- package/all/copy-overwrite/.claude/settings.json +41 -0
- package/all/copy-overwrite/.claude/settings.local.json.example +14 -0
- package/all/copy-overwrite/.claude/skills/coding-philosophy/SKILL.md +405 -0
- package/all/copy-overwrite/.claude/skills/coding-philosophy/references/function-structure.md +416 -0
- package/all/copy-overwrite/.claude/skills/coding-philosophy/references/immutable-patterns.md +316 -0
- package/all/copy-overwrite/.claude/skills/prompt-complexity-scorer/SKILL.md +118 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/LICENSE.txt +202 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/SKILL.md +210 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/scripts/__pycache__/quick_validate.cpython-312.pyc +0 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/scripts/init_skill.py +303 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/scripts/package_skill.py +110 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/scripts/quick_validate.py +65 -0
- package/all/copy-overwrite/CLAUDE.md +77 -0
- package/all/copy-overwrite/HUMAN.md +17 -0
- package/all/copy-overwrite/specs/.keep +0 -0
- package/all/create-only/PROJECT_RULES.md +0 -0
- package/cdk/merge/package.json +20 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +107 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/prompts.d.ts +45 -0
- package/dist/cli/prompts.d.ts.map +1 -0
- package/dist/cli/prompts.js +58 -0
- package/dist/cli/prompts.js.map +1 -0
- package/dist/core/config.d.ts +73 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +36 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +4 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/lisa.d.ts +81 -0
- package/dist/core/lisa.d.ts.map +1 -0
- package/dist/core/lisa.js +459 -0
- package/dist/core/lisa.js.map +1 -0
- package/dist/core/manifest.d.ts +58 -0
- package/dist/core/manifest.d.ts.map +1 -0
- package/dist/core/manifest.js +104 -0
- package/dist/core/manifest.js.map +1 -0
- package/dist/detection/detector.interface.d.ts +15 -0
- package/dist/detection/detector.interface.d.ts.map +1 -0
- package/dist/detection/detector.interface.js +2 -0
- package/dist/detection/detector.interface.js.map +1 -0
- package/dist/detection/detectors/cdk.d.ts +10 -0
- package/dist/detection/detectors/cdk.d.ts.map +1 -0
- package/dist/detection/detectors/cdk.js +34 -0
- package/dist/detection/detectors/cdk.js.map +1 -0
- package/dist/detection/detectors/expo.d.ts +10 -0
- package/dist/detection/detectors/expo.d.ts.map +1 -0
- package/dist/detection/detectors/expo.js +30 -0
- package/dist/detection/detectors/expo.js.map +1 -0
- package/dist/detection/detectors/nestjs.d.ts +10 -0
- package/dist/detection/detectors/nestjs.d.ts.map +1 -0
- package/dist/detection/detectors/nestjs.js +34 -0
- package/dist/detection/detectors/nestjs.js.map +1 -0
- package/dist/detection/detectors/npm-package.d.ts +13 -0
- package/dist/detection/detectors/npm-package.d.ts.map +1 -0
- package/dist/detection/detectors/npm-package.js +30 -0
- package/dist/detection/detectors/npm-package.js.map +1 -0
- package/dist/detection/detectors/typescript.d.ts +10 -0
- package/dist/detection/detectors/typescript.d.ts.map +1 -0
- package/dist/detection/detectors/typescript.js +25 -0
- package/dist/detection/detectors/typescript.js.map +1 -0
- package/dist/detection/index.d.ts +24 -0
- package/dist/detection/index.d.ts.map +1 -0
- package/dist/detection/index.js +57 -0
- package/dist/detection/index.js.map +1 -0
- package/dist/errors/index.d.ts +69 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +110 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/logging/console-logger.d.ts +12 -0
- package/dist/logging/console-logger.d.ts.map +1 -0
- package/dist/logging/console-logger.js +22 -0
- package/dist/logging/console-logger.js.map +1 -0
- package/dist/logging/index.d.ts +4 -0
- package/dist/logging/index.d.ts.map +1 -0
- package/dist/logging/index.js +3 -0
- package/dist/logging/index.js.map +1 -0
- package/dist/logging/logger.interface.d.ts +20 -0
- package/dist/logging/logger.interface.d.ts.map +1 -0
- package/dist/logging/logger.interface.js +2 -0
- package/dist/logging/logger.interface.js.map +1 -0
- package/dist/logging/silent-logger.d.ts +12 -0
- package/dist/logging/silent-logger.d.ts.map +1 -0
- package/dist/logging/silent-logger.js +21 -0
- package/dist/logging/silent-logger.js.map +1 -0
- package/dist/strategies/copy-contents.d.ts +14 -0
- package/dist/strategies/copy-contents.d.ts.map +1 -0
- package/dist/strategies/copy-contents.js +69 -0
- package/dist/strategies/copy-contents.js.map +1 -0
- package/dist/strategies/copy-overwrite.d.ts +14 -0
- package/dist/strategies/copy-overwrite.d.ts.map +1 -0
- package/dist/strategies/copy-overwrite.js +47 -0
- package/dist/strategies/copy-overwrite.js.map +1 -0
- package/dist/strategies/create-only.d.ts +13 -0
- package/dist/strategies/create-only.d.ts.map +1 -0
- package/dist/strategies/create-only.js +30 -0
- package/dist/strategies/create-only.js.map +1 -0
- package/dist/strategies/index.d.ts +31 -0
- package/dist/strategies/index.d.ts.map +1 -0
- package/dist/strategies/index.js +52 -0
- package/dist/strategies/index.js.map +1 -0
- package/dist/strategies/merge.d.ts +13 -0
- package/dist/strategies/merge.d.ts.map +1 -0
- package/dist/strategies/merge.js +60 -0
- package/dist/strategies/merge.js.map +1 -0
- package/dist/strategies/strategy.interface.d.ts +31 -0
- package/dist/strategies/strategy.interface.d.ts.map +1 -0
- package/dist/strategies/strategy.interface.js +2 -0
- package/dist/strategies/strategy.interface.js.map +1 -0
- package/dist/transaction/backup.d.ts +38 -0
- package/dist/transaction/backup.d.ts.map +1 -0
- package/dist/transaction/backup.js +97 -0
- package/dist/transaction/backup.js.map +1 -0
- package/dist/transaction/index.d.ts +4 -0
- package/dist/transaction/index.d.ts.map +1 -0
- package/dist/transaction/index.js +3 -0
- package/dist/transaction/index.js.map +1 -0
- package/dist/transaction/transaction.d.ts +34 -0
- package/dist/transaction/transaction.d.ts.map +1 -0
- package/dist/transaction/transaction.js +68 -0
- package/dist/transaction/transaction.js.map +1 -0
- package/dist/utils/file-operations.d.ts +29 -0
- package/dist/utils/file-operations.d.ts.map +1 -0
- package/dist/utils/file-operations.js +84 -0
- package/dist/utils/file-operations.js.map +1 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +4 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/json-utils.d.ts +22 -0
- package/dist/utils/json-utils.d.ts.map +1 -0
- package/dist/utils/json-utils.js +57 -0
- package/dist/utils/json-utils.js.map +1 -0
- package/dist/utils/path-utils.d.ts +21 -0
- package/dist/utils/path-utils.d.ts.map +1 -0
- package/dist/utils/path-utils.js +35 -0
- package/dist/utils/path-utils.js.map +1 -0
- package/eslint-plugin-code-organization/README.md +149 -0
- package/eslint-plugin-code-organization/__tests__/enforce-statement-order.test.js +468 -0
- package/eslint-plugin-code-organization/index.js +23 -0
- package/eslint-plugin-code-organization/package.json +10 -0
- package/eslint-plugin-code-organization/rules/enforce-statement-order.js +157 -0
- package/expo/copy-overwrite/.claude/skills/apollo-client/SKILL.md +238 -0
- package/expo/copy-overwrite/.claude/skills/apollo-client/references/mutation-patterns.md +360 -0
- package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/SKILL.md +360 -0
- package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/references/atomic-levels.md +417 -0
- package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/references/folder-structure.md +257 -0
- package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/references/gluestack-mapping.md +233 -0
- package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/scripts/validate_atomic_structure.py +327 -0
- package/expo/copy-overwrite/.claude/skills/container-view-pattern/SKILL.md +299 -0
- package/expo/copy-overwrite/.claude/skills/container-view-pattern/references/examples.md +749 -0
- package/expo/copy-overwrite/.claude/skills/container-view-pattern/references/patterns.md +318 -0
- package/expo/copy-overwrite/.claude/skills/container-view-pattern/scripts/create_component.py +198 -0
- package/expo/copy-overwrite/.claude/skills/container-view-pattern/scripts/validate_component.py +207 -0
- package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/SKILL.md +268 -0
- package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/references/common-issues.md +619 -0
- package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/references/file-extensions.md +340 -0
- package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/references/platform-api.md +276 -0
- package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/scripts/validate_cross_platform.py +414 -0
- package/expo/copy-overwrite/.claude/skills/directory-structure/SKILL.md +202 -0
- package/expo/copy-overwrite/.claude/skills/directory-structure/scripts/validate_structure.py +443 -0
- package/expo/copy-overwrite/.claude/skills/expo-env-config/SKILL.md +309 -0
- package/expo/copy-overwrite/.claude/skills/expo-env-config/references/validation-patterns.md +417 -0
- package/expo/copy-overwrite/.claude/skills/expo-router-best-practices/SKILL.md +431 -0
- package/expo/copy-overwrite/.claude/skills/expo-router-best-practices/references/official-docs.md +290 -0
- package/expo/copy-overwrite/.claude/skills/expo-router-best-practices/scripts/generate-route.py +169 -0
- package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/SKILL.md +411 -0
- package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/references/color-tokens.md +343 -0
- package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/references/component-mapping.md +307 -0
- package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/references/spacing-scale.md +300 -0
- package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/scripts/validate_styling.py +354 -0
- package/expo/copy-overwrite/.claude/skills/local-state/SKILL.md +362 -0
- package/expo/copy-overwrite/.claude/skills/local-state/references/async-storage.md +505 -0
- package/expo/copy-overwrite/.claude/skills/local-state/references/persistence-patterns.md +711 -0
- package/expo/copy-overwrite/.claude/skills/local-state/references/reactive-variables.md +446 -0
- package/expo/copy-overwrite/.claude/skills/playwright-selectors/SKILL.md +223 -0
- package/expo/copy-overwrite/.claude/skills/testing-library/SKILL.md +319 -0
- package/expo/copy-overwrite/.claude/skills/testing-library/references/async-patterns.md +420 -0
- package/expo/copy-overwrite/.claude/skills/testing-library/references/expo-router-testing.md +556 -0
- package/expo/copy-overwrite/.claude/skills/testing-library/references/mocking-patterns.md +590 -0
- package/expo/copy-overwrite/.claude/skills/testing-library/references/query-priority.md +291 -0
- package/expo/copy-overwrite/.easignore.extra +2 -0
- package/expo/copy-overwrite/.mcp.json +33 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/README.md +234 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/__tests__/plugin-index.test.js +84 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/__tests__/require-memo-in-view.test.js +196 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/__tests__/single-component-per-file.test.js +289 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/index.js +32 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/package.json +10 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/rules/enforce-component-structure.js +230 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/rules/no-return-in-view.js +91 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/rules/require-memo-in-view.js +178 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/rules/single-component-per-file.js +238 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/README.md +260 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/index.js +29 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/package.json +10 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/rules/no-classname-outside-ui.js +51 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/rules/no-direct-rn-imports.js +55 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/rules/no-inline-styles.js +73 -0
- package/expo/copy-overwrite/eslint.config.mjs +560 -0
- package/expo/copy-overwrite/lighthouserc.js +194 -0
- package/expo/create-only/lighthouserc-config.json +28 -0
- package/expo/merge/package.json +132 -0
- package/lisa.sh +35 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/SKILL.md +176 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/advanced-features.md +527 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/project-patterns.md +483 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/quick-start.md +257 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/resolvers-mutations.md +413 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/types-scalars.md +513 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-rules/SKILL.md +536 -0
- package/nestjs/copy-overwrite/.claude/skills/typeorm-patterns/SKILL.md +275 -0
- package/nestjs/copy-overwrite/.claude/skills/typeorm-patterns/references/configuration-patterns.md +487 -0
- package/nestjs/copy-overwrite/.claude/skills/typeorm-patterns/references/entity-patterns.md +450 -0
- package/nestjs/copy-overwrite/.claude/skills/typeorm-patterns/references/observability-patterns.md +536 -0
- package/nestjs/merge/package.json +75 -0
- package/package.json +124 -0
- package/typescript/copy-contents/.husky/commit-msg +91 -0
- package/typescript/copy-contents/.husky/pre-commit +96 -0
- package/typescript/copy-contents/.husky/pre-push +211 -0
- package/typescript/copy-overwrite/.claude/hooks/format-on-edit.sh +74 -0
- package/typescript/copy-overwrite/.claude/hooks/install_pkgs.sh +59 -0
- package/typescript/copy-overwrite/.claude/hooks/lint-on-edit.sh +103 -0
- package/typescript/copy-overwrite/.claude/skills/jsdoc-best-practices/SKILL.md +388 -0
- package/typescript/copy-overwrite/.github/README.md +455 -0
- package/typescript/copy-overwrite/.github/dependabot.yml +40 -0
- package/typescript/copy-overwrite/.github/k6/BROWSER_TESTING_NOTE.md +129 -0
- package/typescript/copy-overwrite/.github/k6/INTEGRATION_GUIDE.md +354 -0
- package/typescript/copy-overwrite/.github/k6/README.md +386 -0
- package/typescript/copy-overwrite/.github/k6/SCENARIO_SELECTION_GUIDE.md +264 -0
- package/typescript/copy-overwrite/.github/k6/examples/customer-deploy-integration.yml +115 -0
- package/typescript/copy-overwrite/.github/k6/examples/data-driven-test.js +268 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/load.js +142 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/load.json +27 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/smoke.js +26 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/smoke.json +20 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/soak.js +244 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/soak.json +29 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/spike.js +180 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/spike.json +32 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/stress.js +206 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/stress.json +38 -0
- package/typescript/copy-overwrite/.github/k6/scripts/api-test.js +452 -0
- package/typescript/copy-overwrite/.github/k6/scripts/default-test.js +185 -0
- package/typescript/copy-overwrite/.github/k6/thresholds/normal.json +30 -0
- package/typescript/copy-overwrite/.github/k6/thresholds/relaxed.json +21 -0
- package/typescript/copy-overwrite/.github/k6/thresholds/strict.json +29 -0
- package/typescript/copy-overwrite/.github/workflows/build.yml +72 -0
- package/typescript/copy-overwrite/.github/workflows/ci.yml +49 -0
- package/typescript/copy-overwrite/.github/workflows/claude.yml +51 -0
- package/typescript/copy-overwrite/.github/workflows/create-github-issue-on-failure.yml +113 -0
- package/typescript/copy-overwrite/.github/workflows/create-jira-issue-on-failure.yml +195 -0
- package/typescript/copy-overwrite/.github/workflows/create-sentry-issue-on-failure.yml +267 -0
- package/typescript/copy-overwrite/.github/workflows/deploy.yml +228 -0
- package/typescript/copy-overwrite/.github/workflows/k6-load-test-README.md +230 -0
- package/typescript/copy-overwrite/.github/workflows/lighthouse.yml +68 -0
- package/typescript/copy-overwrite/.github/workflows/load-test.yml +282 -0
- package/typescript/copy-overwrite/.github/workflows/quality.yml +1737 -0
- package/typescript/copy-overwrite/.github/workflows/release.yml +1599 -0
- package/typescript/copy-overwrite/.gitleaksignore +28 -0
- package/typescript/copy-overwrite/.nvmrc +1 -0
- package/typescript/copy-overwrite/.prettierignore +23 -0
- package/typescript/copy-overwrite/.prettierrc.json +22 -0
- package/typescript/copy-overwrite/.versionrc +42 -0
- package/typescript/copy-overwrite/.yamllint +20 -0
- package/typescript/copy-overwrite/commitlint.config.js +11 -0
- package/typescript/copy-overwrite/eslint-plugin-code-organization/README.md +149 -0
- package/typescript/copy-overwrite/eslint-plugin-code-organization/__tests__/enforce-statement-order.test.js +468 -0
- package/typescript/copy-overwrite/eslint-plugin-code-organization/index.js +23 -0
- package/typescript/copy-overwrite/eslint-plugin-code-organization/package.json +10 -0
- package/typescript/copy-overwrite/eslint-plugin-code-organization/rules/enforce-statement-order.js +157 -0
- package/typescript/copy-overwrite/eslint.config.mjs +390 -0
- package/typescript/copy-overwrite/eslint.ignore.config.json +57 -0
- package/typescript/copy-overwrite/eslint.thresholds.config.json +5 -0
- package/typescript/github-rulesets/base.json +106 -0
- package/typescript/merge/.claude/settings.json +28 -0
- package/typescript/merge/package.json +71 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import http from "k6/http";
|
|
2
|
+
import { check, group, sleep } from "k6";
|
|
3
|
+
import { Rate, Trend, Counter } from "k6/metrics";
|
|
4
|
+
|
|
5
|
+
// Custom metrics for soak testing
|
|
6
|
+
const errorRate = new Rate("errors");
|
|
7
|
+
const memoryLeakIndicator = new Trend("response_time_degradation");
|
|
8
|
+
const requestsPerMinute = new Counter("requests_per_minute");
|
|
9
|
+
const degradationRate = new Trend("degradation_rate");
|
|
10
|
+
|
|
11
|
+
export const options = {
|
|
12
|
+
vus: 10,
|
|
13
|
+
duration: __ENV.K6_DURATION || "30m", // Default 30 minutes, can override
|
|
14
|
+
thresholds: {
|
|
15
|
+
http_req_failed: ["rate<0.02"], // Very low error tolerance for soak
|
|
16
|
+
http_req_duration: ["p(95)<1000"], // Consistent performance expected
|
|
17
|
+
errors: ["rate<0.02"],
|
|
18
|
+
degradation_rate: ["value<0.1"], // Performance shouldn't degrade > 10%
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const BASE_URL = __ENV.K6_BASE_URL || "http://localhost:3000";
|
|
23
|
+
const CUSTOM_HEADERS = __ENV.K6_CUSTOM_HEADERS
|
|
24
|
+
? JSON.parse(__ENV.K6_CUSTOM_HEADERS)
|
|
25
|
+
: {};
|
|
26
|
+
|
|
27
|
+
// Track performance over time
|
|
28
|
+
const performanceHistory = [];
|
|
29
|
+
const historyInterval = 60; // Track every 60 seconds
|
|
30
|
+
let lastHistoryUpdate = Date.now();
|
|
31
|
+
let baselineResponseTime = null;
|
|
32
|
+
|
|
33
|
+
export default function () {
|
|
34
|
+
const headers = {
|
|
35
|
+
"Content-Type": "application/json",
|
|
36
|
+
...CUSTOM_HEADERS,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const iterationStart = Date.now();
|
|
40
|
+
|
|
41
|
+
group("Soak Test Operations", () => {
|
|
42
|
+
// Standard operations that run continuously
|
|
43
|
+
group("Read Operations", () => {
|
|
44
|
+
const endpoints = [
|
|
45
|
+
"/api/items",
|
|
46
|
+
"/api/status",
|
|
47
|
+
"/api/health",
|
|
48
|
+
"/api/metrics",
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
endpoints.forEach(endpoint => {
|
|
52
|
+
const response = http.get(`${BASE_URL}${endpoint}`, {
|
|
53
|
+
headers,
|
|
54
|
+
tags: { name: `GET ${endpoint}`, operation: "read" },
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const isOk = check(response, {
|
|
58
|
+
[`${endpoint} status OK`]: r => r.status === 200,
|
|
59
|
+
[`${endpoint} response time OK`]: r => r.timings.duration < 1000,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
if (!isOk) {
|
|
63
|
+
errorRate.add(1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Track response time degradation
|
|
67
|
+
if (endpoint === "/api/items") {
|
|
68
|
+
memoryLeakIndicator.add(response.timings.duration);
|
|
69
|
+
|
|
70
|
+
// Set baseline on first successful request
|
|
71
|
+
if (baselineResponseTime === null && response.status === 200) {
|
|
72
|
+
baselineResponseTime = response.timings.duration;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Calculate degradation
|
|
76
|
+
if (baselineResponseTime !== null) {
|
|
77
|
+
const degradation =
|
|
78
|
+
(response.timings.duration - baselineResponseTime) /
|
|
79
|
+
baselineResponseTime;
|
|
80
|
+
degradationRate.add(degradation);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
group("Write Operations", () => {
|
|
87
|
+
// Periodic write operations to test resource cleanup
|
|
88
|
+
if (__ITER % 10 === 0) {
|
|
89
|
+
// Every 10th iteration
|
|
90
|
+
const payload = JSON.stringify({
|
|
91
|
+
test: "soak",
|
|
92
|
+
iteration: __ITER,
|
|
93
|
+
timestamp: Date.now(),
|
|
94
|
+
vu: __VU,
|
|
95
|
+
data: Array(100).fill("x").join(""), // Small payload
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const writeResponse = http.post(`${BASE_URL}/api/items`, payload, {
|
|
99
|
+
headers,
|
|
100
|
+
tags: { name: "POST /api/items", operation: "write" },
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
check(writeResponse, {
|
|
104
|
+
"write status OK": r => r.status === 201 || r.status === 200,
|
|
105
|
+
"write response time OK": r => r.timings.duration < 2000,
|
|
106
|
+
}) || errorRate.add(1);
|
|
107
|
+
|
|
108
|
+
// Test cleanup - delete old items periodically
|
|
109
|
+
if (__ITER % 50 === 0) {
|
|
110
|
+
const deleteResponse = http.del(`${BASE_URL}/api/items/old`, {
|
|
111
|
+
headers,
|
|
112
|
+
tags: { name: "DELETE old items", operation: "cleanup" },
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
check(deleteResponse, {
|
|
116
|
+
"cleanup successful": r => r.status === 200 || r.status === 204,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
group("Complex Operations", () => {
|
|
123
|
+
// Simulate complex user workflows
|
|
124
|
+
if (__ITER % 5 === 0) {
|
|
125
|
+
// Every 5th iteration
|
|
126
|
+
const searchResponse = http.get(
|
|
127
|
+
`${BASE_URL}/api/search?q=test&limit=100`,
|
|
128
|
+
{
|
|
129
|
+
headers,
|
|
130
|
+
tags: { name: "Complex Search", operation: "search" },
|
|
131
|
+
}
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
check(searchResponse, {
|
|
135
|
+
"search completed": r => r.status === 200,
|
|
136
|
+
"search performance OK": r => r.timings.duration < 3000,
|
|
137
|
+
}) || errorRate.add(1);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Track requests per minute
|
|
143
|
+
requestsPerMinute.add(1);
|
|
144
|
+
|
|
145
|
+
// Update performance history
|
|
146
|
+
const now = Date.now();
|
|
147
|
+
if (now - lastHistoryUpdate > historyInterval * 1000) {
|
|
148
|
+
performanceHistory.push({
|
|
149
|
+
timestamp: now,
|
|
150
|
+
avgResponseTime: memoryLeakIndicator.value,
|
|
151
|
+
errorRate: errorRate.value,
|
|
152
|
+
});
|
|
153
|
+
lastHistoryUpdate = now;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Consistent pacing for soak test
|
|
157
|
+
const iterationDuration = Date.now() - iterationStart;
|
|
158
|
+
const targetPace = 1000; // 1 request per second per VU
|
|
159
|
+
const sleepTime = Math.max(0, targetPace - iterationDuration) / 1000;
|
|
160
|
+
sleep(sleepTime);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function handleSummary(data) {
|
|
164
|
+
const { metrics } = data;
|
|
165
|
+
const duration = parseInt(__ENV.K6_DURATION || "30m");
|
|
166
|
+
|
|
167
|
+
const summary = {
|
|
168
|
+
timestamp: new Date().toISOString(),
|
|
169
|
+
test_type: "soak",
|
|
170
|
+
duration: __ENV.K6_DURATION || "30m",
|
|
171
|
+
total_requests: metrics.http_reqs?.values?.count || 0,
|
|
172
|
+
failed_requests: metrics.http_req_failed?.values?.passes || 0,
|
|
173
|
+
error_rate: metrics.errors?.values?.rate || 0,
|
|
174
|
+
performance: {
|
|
175
|
+
baseline_response_time: baselineResponseTime,
|
|
176
|
+
final_avg_response_time: Math.round(
|
|
177
|
+
metrics.http_req_duration?.values?.avg || 0
|
|
178
|
+
),
|
|
179
|
+
p95_response_time: Math.round(
|
|
180
|
+
metrics.http_req_duration?.values["p(95)"] || 0
|
|
181
|
+
),
|
|
182
|
+
p99_response_time: Math.round(
|
|
183
|
+
metrics.http_req_duration?.values["p(99)"] || 0
|
|
184
|
+
),
|
|
185
|
+
degradation_percentage:
|
|
186
|
+
(metrics.degradation_rate?.values?.avg || 0) * 100,
|
|
187
|
+
},
|
|
188
|
+
resource_usage: {
|
|
189
|
+
requests_per_minute: Math.round(
|
|
190
|
+
(metrics.http_reqs?.values?.count || 0) / (duration / 60)
|
|
191
|
+
),
|
|
192
|
+
avg_data_sent: Math.round(metrics.data_sent?.values?.avg || 0),
|
|
193
|
+
avg_data_received: Math.round(metrics.data_received?.values?.avg || 0),
|
|
194
|
+
},
|
|
195
|
+
stability: {
|
|
196
|
+
consistent_performance: metrics.degradation_rate?.values?.avg < 0.1,
|
|
197
|
+
low_error_rate: metrics.errors?.values?.rate < 0.02,
|
|
198
|
+
thresholds_passed: Object.entries(data.thresholds || {}).every(
|
|
199
|
+
([, value]) => value.ok
|
|
200
|
+
),
|
|
201
|
+
},
|
|
202
|
+
performance_history: performanceHistory,
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
"k6-results/soak-summary.json": JSON.stringify(summary, null, 2),
|
|
207
|
+
stdout: createSoakSummary(summary),
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function createSoakSummary(summary) {
|
|
212
|
+
let text = "\n=== Soak Test Summary ===\n\n";
|
|
213
|
+
text += `Test Duration: ${summary.duration}\n`;
|
|
214
|
+
text += `Timestamp: ${summary.timestamp}\n\n`;
|
|
215
|
+
|
|
216
|
+
text += "Overall Statistics:\n";
|
|
217
|
+
text += ` Total Requests: ${summary.total_requests}\n`;
|
|
218
|
+
text += ` Failed Requests: ${summary.failed_requests}\n`;
|
|
219
|
+
text += ` Error Rate: ${(summary.error_rate * 100).toFixed(2)}%\n\n`;
|
|
220
|
+
|
|
221
|
+
text += "Performance Analysis:\n";
|
|
222
|
+
text += ` Baseline Response Time: ${summary.performance.baseline_response_time}ms\n`;
|
|
223
|
+
text += ` Final Avg Response Time: ${summary.performance.final_avg_response_time}ms\n`;
|
|
224
|
+
text += ` 95th Percentile: ${summary.performance.p95_response_time}ms\n`;
|
|
225
|
+
text += ` 99th Percentile: ${summary.performance.p99_response_time}ms\n`;
|
|
226
|
+
text += ` Performance Degradation: ${summary.performance.degradation_percentage.toFixed(2)}%\n\n`;
|
|
227
|
+
|
|
228
|
+
text += "Resource Usage:\n";
|
|
229
|
+
text += ` Requests/Minute: ${summary.resource_usage.requests_per_minute}\n`;
|
|
230
|
+
text += ` Avg Data Sent: ${(summary.resource_usage.avg_data_sent / 1024).toFixed(2)} KB\n`;
|
|
231
|
+
text += ` Avg Data Received: ${(summary.resource_usage.avg_data_received / 1024).toFixed(2)} KB\n\n`;
|
|
232
|
+
|
|
233
|
+
text += "Stability Assessment:\n";
|
|
234
|
+
text += ` Consistent Performance: ${summary.stability.consistent_performance ? "✅ YES" : "❌ NO"}\n`;
|
|
235
|
+
text += ` Low Error Rate: ${summary.stability.low_error_rate ? "✅ YES" : "❌ NO"}\n`;
|
|
236
|
+
text += ` All Thresholds Passed: ${summary.stability.thresholds_passed ? "✅ YES" : "❌ NO"}\n`;
|
|
237
|
+
|
|
238
|
+
if (!summary.stability.consistent_performance) {
|
|
239
|
+
text +=
|
|
240
|
+
"\n⚠️ WARNING: Performance degradation detected over time. Possible memory leak or resource exhaustion.\n";
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return text;
|
|
244
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "soak",
|
|
3
|
+
"description": "Extended duration test to identify memory leaks and performance degradation",
|
|
4
|
+
"executor": "constant-vus",
|
|
5
|
+
"vus": 10,
|
|
6
|
+
"duration": "30m",
|
|
7
|
+
"thresholds": {
|
|
8
|
+
"http_req_failed": ["rate<0.02"],
|
|
9
|
+
"http_req_duration": ["p(95)<1000", "p(99)<2000"],
|
|
10
|
+
"http_req_receiving": ["p(95)<500"],
|
|
11
|
+
"checks": ["rate>0.95"],
|
|
12
|
+
"iterations": ["count>1000"],
|
|
13
|
+
"vus": ["value==10"]
|
|
14
|
+
},
|
|
15
|
+
"env": {
|
|
16
|
+
"SCENARIO_NAME": "soak",
|
|
17
|
+
"SOAK_DURATION": "30m",
|
|
18
|
+
"MEMORY_CHECK_INTERVAL": "5m"
|
|
19
|
+
},
|
|
20
|
+
"tags": {
|
|
21
|
+
"test_type": "soak",
|
|
22
|
+
"environment": "${K6_ENVIRONMENT}",
|
|
23
|
+
"long_running": "true"
|
|
24
|
+
},
|
|
25
|
+
"notes": {
|
|
26
|
+
"monitoring": "Monitor memory usage, CPU, and response time trends over the duration",
|
|
27
|
+
"duration_override": "Can be extended to 1h, 2h, or 4h based on requirements"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import http from "k6/http";
|
|
2
|
+
import { check, group, sleep } from "k6";
|
|
3
|
+
import { Rate, Trend } from "k6/metrics";
|
|
4
|
+
|
|
5
|
+
// Custom metrics
|
|
6
|
+
const errorRate = new Rate("errors");
|
|
7
|
+
const spikeResponseTime = new Trend("spike_response_time");
|
|
8
|
+
const recoveryTime = new Trend("recovery_time");
|
|
9
|
+
|
|
10
|
+
export const options = {
|
|
11
|
+
stages: [
|
|
12
|
+
{ duration: "30s", target: 5 }, // Baseline load
|
|
13
|
+
{ duration: "1m", target: 5 }, // Stay at baseline
|
|
14
|
+
{ duration: "30s", target: 100 }, // Spike to 100 users
|
|
15
|
+
{ duration: "3m", target: 100 }, // Stay at peak
|
|
16
|
+
{ duration: "30s", target: 5 }, // Drop back to baseline
|
|
17
|
+
{ duration: "2m", target: 5 }, // Recovery period
|
|
18
|
+
{ duration: "30s", target: 0 }, // Ramp down
|
|
19
|
+
],
|
|
20
|
+
thresholds: {
|
|
21
|
+
http_req_failed: ["rate<0.15"], // Higher tolerance during spikes
|
|
22
|
+
http_req_duration: ["p(95)<3000"], // 95% of requests under 3s
|
|
23
|
+
errors: ["rate<0.2"], // 20% error rate threshold
|
|
24
|
+
spike_response_time: ["p(95)<5000"], // Spike-specific metric
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const BASE_URL = __ENV.K6_BASE_URL || "http://localhost:3000";
|
|
29
|
+
const CUSTOM_HEADERS = __ENV.K6_CUSTOM_HEADERS
|
|
30
|
+
? JSON.parse(__ENV.K6_CUSTOM_HEADERS)
|
|
31
|
+
: {};
|
|
32
|
+
|
|
33
|
+
// Track test phases
|
|
34
|
+
let currentPhase = "baseline";
|
|
35
|
+
let phaseStartTime = Date.now();
|
|
36
|
+
|
|
37
|
+
export default function () {
|
|
38
|
+
const headers = {
|
|
39
|
+
"Content-Type": "application/json",
|
|
40
|
+
...CUSTOM_HEADERS,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Determine current phase based on VU count
|
|
44
|
+
const vuCount = __VU;
|
|
45
|
+
let phase = "baseline";
|
|
46
|
+
if (vuCount > 50) phase = "spike";
|
|
47
|
+
else if (vuCount <= 10 && currentPhase === "spike") phase = "recovery";
|
|
48
|
+
|
|
49
|
+
if (phase !== currentPhase) {
|
|
50
|
+
const phaseDuration = Date.now() - phaseStartTime;
|
|
51
|
+
if (currentPhase === "spike") {
|
|
52
|
+
recoveryTime.add(phaseDuration);
|
|
53
|
+
}
|
|
54
|
+
currentPhase = phase;
|
|
55
|
+
phaseStartTime = Date.now();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
group(`Spike Test - ${phase} phase`, () => {
|
|
59
|
+
// Critical user path that must handle spikes
|
|
60
|
+
const criticalResponse = http.get(`${BASE_URL}/api/critical-endpoint`, {
|
|
61
|
+
headers,
|
|
62
|
+
tags: { name: "CriticalPath", phase: phase },
|
|
63
|
+
timeout: "10s", // Longer timeout during spikes
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const criticalCheck = check(criticalResponse, {
|
|
67
|
+
"critical path available": r => r.status === 200 || r.status === 503,
|
|
68
|
+
"response time acceptable": r =>
|
|
69
|
+
r.timings.duration < (phase === "spike" ? 5000 : 1000),
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
if (!criticalCheck) {
|
|
73
|
+
errorRate.add(1);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (phase === "spike") {
|
|
77
|
+
spikeResponseTime.add(criticalResponse.timings.duration);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Simulate various user behaviors during spike
|
|
81
|
+
if (Math.random() < 0.7) {
|
|
82
|
+
// 70% read operations
|
|
83
|
+
const readResponse = http.get(`${BASE_URL}/api/items`, {
|
|
84
|
+
headers,
|
|
85
|
+
tags: { name: "ReadOperation", phase: phase },
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
check(readResponse, {
|
|
89
|
+
"read operation successful": r => r.status === 200,
|
|
90
|
+
}) || errorRate.add(1);
|
|
91
|
+
} else {
|
|
92
|
+
// 30% write operations
|
|
93
|
+
const writePayload = JSON.stringify({
|
|
94
|
+
action: "spike_test",
|
|
95
|
+
timestamp: Date.now(),
|
|
96
|
+
phase: phase,
|
|
97
|
+
vu: __VU,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const writeResponse = http.post(`${BASE_URL}/api/items`, writePayload, {
|
|
101
|
+
headers,
|
|
102
|
+
tags: { name: "WriteOperation", phase: phase },
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
check(writeResponse, {
|
|
106
|
+
"write operation handled": r => [200, 201, 429, 503].includes(r.status),
|
|
107
|
+
}) || errorRate.add(1);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Minimal sleep during spike phase, normal sleep otherwise
|
|
112
|
+
sleep(phase === "spike" ? 0.1 : 1);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function handleSummary(data) {
|
|
116
|
+
const { metrics } = data;
|
|
117
|
+
|
|
118
|
+
const summary = {
|
|
119
|
+
timestamp: new Date().toISOString(),
|
|
120
|
+
test_type: "spike",
|
|
121
|
+
phases: {
|
|
122
|
+
baseline: {
|
|
123
|
+
avg_response_time: Math.round(
|
|
124
|
+
metrics.http_req_duration?.values?.avg || 0
|
|
125
|
+
),
|
|
126
|
+
error_rate: metrics.errors?.values?.rate || 0,
|
|
127
|
+
},
|
|
128
|
+
spike: {
|
|
129
|
+
avg_response_time: Math.round(
|
|
130
|
+
metrics.spike_response_time?.values?.avg || 0
|
|
131
|
+
),
|
|
132
|
+
p95_response_time: Math.round(
|
|
133
|
+
metrics.spike_response_time?.values["p(95)"] || 0
|
|
134
|
+
),
|
|
135
|
+
max_response_time: Math.round(
|
|
136
|
+
metrics.spike_response_time?.values?.max || 0
|
|
137
|
+
),
|
|
138
|
+
error_rate: metrics.errors?.values?.rate || 0,
|
|
139
|
+
},
|
|
140
|
+
recovery: {
|
|
141
|
+
time_to_normal: Math.round(metrics.recovery_time?.values?.avg || 0),
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
total_requests: metrics.http_reqs?.values?.count || 0,
|
|
145
|
+
failed_requests: metrics.http_req_failed?.values?.passes || 0,
|
|
146
|
+
thresholds_passed: Object.entries(data.thresholds || {}).every(
|
|
147
|
+
([, value]) => value.ok
|
|
148
|
+
),
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
"k6-results/spike-summary.json": JSON.stringify(summary, null, 2),
|
|
153
|
+
stdout: createSpikeSummary(summary),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function createSpikeSummary(summary) {
|
|
158
|
+
let text = "\n=== Spike Test Summary ===\n\n";
|
|
159
|
+
text += `Timestamp: ${summary.timestamp}\n`;
|
|
160
|
+
text += `Total Requests: ${summary.total_requests}\n`;
|
|
161
|
+
text += `Failed Requests: ${summary.failed_requests}\n\n`;
|
|
162
|
+
|
|
163
|
+
text += "Phase Analysis:\n";
|
|
164
|
+
text += ` Baseline Phase:\n`;
|
|
165
|
+
text += ` - Avg Response Time: ${summary.phases.baseline.avg_response_time}ms\n`;
|
|
166
|
+
text += ` - Error Rate: ${(summary.phases.baseline.error_rate * 100).toFixed(2)}%\n`;
|
|
167
|
+
|
|
168
|
+
text += ` Spike Phase:\n`;
|
|
169
|
+
text += ` - Avg Response Time: ${summary.phases.spike.avg_response_time}ms\n`;
|
|
170
|
+
text += ` - 95th Percentile: ${summary.phases.spike.p95_response_time}ms\n`;
|
|
171
|
+
text += ` - Max Response Time: ${summary.phases.spike.max_response_time}ms\n`;
|
|
172
|
+
text += ` - Error Rate: ${(summary.phases.spike.error_rate * 100).toFixed(2)}%\n`;
|
|
173
|
+
|
|
174
|
+
text += ` Recovery Phase:\n`;
|
|
175
|
+
text += ` - Time to Normal: ${summary.phases.recovery.time_to_normal}ms\n\n`;
|
|
176
|
+
|
|
177
|
+
text += `All Thresholds Passed: ${summary.thresholds_passed ? "✅ YES" : "❌ NO"}\n`;
|
|
178
|
+
|
|
179
|
+
return text;
|
|
180
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "spike",
|
|
3
|
+
"description": "Test system's ability to handle sudden traffic spikes",
|
|
4
|
+
"executor": "ramping-vus",
|
|
5
|
+
"startVUs": 0,
|
|
6
|
+
"stages": [
|
|
7
|
+
{ "duration": "30s", "target": 5 },
|
|
8
|
+
{ "duration": "1m", "target": 5 },
|
|
9
|
+
{ "duration": "30s", "target": 100 },
|
|
10
|
+
{ "duration": "3m", "target": 100 },
|
|
11
|
+
{ "duration": "30s", "target": 5 },
|
|
12
|
+
{ "duration": "2m", "target": 5 },
|
|
13
|
+
{ "duration": "30s", "target": 0 }
|
|
14
|
+
],
|
|
15
|
+
"gracefulStop": "30s",
|
|
16
|
+
"thresholds": {
|
|
17
|
+
"http_req_failed": ["rate<0.15"],
|
|
18
|
+
"http_req_duration": ["p(95)<3000", "p(99)<10000"],
|
|
19
|
+
"http_req_receiving": ["p(95)<2000"],
|
|
20
|
+
"checks": ["rate>0.75"],
|
|
21
|
+
"http_req_waiting": ["p(95)<5000"]
|
|
22
|
+
},
|
|
23
|
+
"env": {
|
|
24
|
+
"SCENARIO_NAME": "spike",
|
|
25
|
+
"SPIKE_MULTIPLIER": "20",
|
|
26
|
+
"RECOVERY_TIME": "2m"
|
|
27
|
+
},
|
|
28
|
+
"tags": {
|
|
29
|
+
"test_type": "spike",
|
|
30
|
+
"environment": "${K6_ENVIRONMENT}"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import http from "k6/http";
|
|
2
|
+
import { check, group, sleep } from "k6";
|
|
3
|
+
import { Rate, Trend } from "k6/metrics";
|
|
4
|
+
|
|
5
|
+
// Custom metrics
|
|
6
|
+
const errorRate = new Rate("errors");
|
|
7
|
+
const responseTime = new Trend("custom_response_time");
|
|
8
|
+
const throughput = new Rate("throughput");
|
|
9
|
+
|
|
10
|
+
export const options = {
|
|
11
|
+
stages: [
|
|
12
|
+
{ duration: "2m", target: 10 }, // Warm up
|
|
13
|
+
{ duration: "5m", target: 10 }, // Normal load
|
|
14
|
+
{ duration: "2m", target: 20 }, // Increase to 20 users
|
|
15
|
+
{ duration: "5m", target: 20 }, // Stay at 20 users
|
|
16
|
+
{ duration: "2m", target: 30 }, // Increase to 30 users
|
|
17
|
+
{ duration: "5m", target: 30 }, // Stay at 30 users
|
|
18
|
+
{ duration: "5m", target: 0 }, // Ramp down
|
|
19
|
+
],
|
|
20
|
+
thresholds: {
|
|
21
|
+
http_req_failed: [
|
|
22
|
+
{
|
|
23
|
+
threshold: "rate<0.1",
|
|
24
|
+
abortOnFail: true,
|
|
25
|
+
delayAbortEval: "30s",
|
|
26
|
+
},
|
|
27
|
+
], // Abort if error rate > 10%
|
|
28
|
+
http_req_duration: ["p(95)<1000", "p(99)<2000"], // More relaxed for stress
|
|
29
|
+
errors: ["rate<0.15"], // Allow up to 15% errors under stress
|
|
30
|
+
throughput: ["rate>0.8"], // At least 80% of requests should complete
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const BASE_URL = __ENV.K6_BASE_URL || "http://localhost:3000";
|
|
35
|
+
const CUSTOM_HEADERS = __ENV.K6_CUSTOM_HEADERS
|
|
36
|
+
? JSON.parse(__ENV.K6_CUSTOM_HEADERS)
|
|
37
|
+
: {};
|
|
38
|
+
|
|
39
|
+
export default function () {
|
|
40
|
+
const headers = {
|
|
41
|
+
"Content-Type": "application/json",
|
|
42
|
+
...CUSTOM_HEADERS,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// Stress test focuses on system behavior under high load
|
|
46
|
+
group("Concurrent API Calls", () => {
|
|
47
|
+
const batch = http.batch([
|
|
48
|
+
[
|
|
49
|
+
"GET",
|
|
50
|
+
`${BASE_URL}/api/items`,
|
|
51
|
+
null,
|
|
52
|
+
{ headers, tags: { name: "BatchGet1" } },
|
|
53
|
+
],
|
|
54
|
+
[
|
|
55
|
+
"GET",
|
|
56
|
+
`${BASE_URL}/api/items?page=2`,
|
|
57
|
+
null,
|
|
58
|
+
{ headers, tags: { name: "BatchGet2" } },
|
|
59
|
+
],
|
|
60
|
+
[
|
|
61
|
+
"GET",
|
|
62
|
+
`${BASE_URL}/api/status`,
|
|
63
|
+
null,
|
|
64
|
+
{ headers, tags: { name: "BatchStatus" } },
|
|
65
|
+
],
|
|
66
|
+
]);
|
|
67
|
+
|
|
68
|
+
batch.forEach((response, index) => {
|
|
69
|
+
const isOk = check(response, {
|
|
70
|
+
"batch request succeeded": r => r.status === 200,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
if (!isOk) {
|
|
74
|
+
errorRate.add(1);
|
|
75
|
+
} else {
|
|
76
|
+
throughput.add(1);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
responseTime.add(response.timings.duration);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Heavy operations
|
|
84
|
+
group("Resource Intensive Operations", () => {
|
|
85
|
+
// Simulate large payload
|
|
86
|
+
const largePayload = JSON.stringify({
|
|
87
|
+
data: Array(100)
|
|
88
|
+
.fill(null)
|
|
89
|
+
.map((_, i) => ({
|
|
90
|
+
id: i,
|
|
91
|
+
name: `Item ${i}`,
|
|
92
|
+
description:
|
|
93
|
+
"Lorem ipsum dolor sit amet, consectetur adipiscing elit".repeat(
|
|
94
|
+
10
|
|
95
|
+
),
|
|
96
|
+
metadata: {
|
|
97
|
+
created: new Date().toISOString(),
|
|
98
|
+
tags: Array(20)
|
|
99
|
+
.fill(null)
|
|
100
|
+
.map((_, j) => `tag${j}`),
|
|
101
|
+
},
|
|
102
|
+
})),
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const heavyResponse = http.post(
|
|
106
|
+
`${BASE_URL}/api/bulk-process`,
|
|
107
|
+
largePayload,
|
|
108
|
+
{
|
|
109
|
+
headers,
|
|
110
|
+
tags: { name: "HeavyOperation" },
|
|
111
|
+
timeout: "30s",
|
|
112
|
+
}
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const heavyCheck = check(heavyResponse, {
|
|
116
|
+
"heavy operation completed": r => r.status === 200 || r.status === 202,
|
|
117
|
+
"heavy operation within timeout": r => r.timings.duration < 30000,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
if (!heavyCheck) {
|
|
121
|
+
errorRate.add(1);
|
|
122
|
+
} else {
|
|
123
|
+
throughput.add(1);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Rapid fire requests (no sleep)
|
|
128
|
+
group("Rapid Requests", () => {
|
|
129
|
+
for (let i = 0; i < 5; i++) {
|
|
130
|
+
const rapidResponse = http.get(
|
|
131
|
+
`${BASE_URL}/api/items/${Math.floor(Math.random() * 100)}`,
|
|
132
|
+
{
|
|
133
|
+
headers,
|
|
134
|
+
tags: { name: "RapidFire" },
|
|
135
|
+
}
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
check(rapidResponse, {
|
|
139
|
+
"rapid request handled": r => r.status === 200 || r.status === 404,
|
|
140
|
+
}) || errorRate.add(1);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// Minimal think time to maintain pressure
|
|
145
|
+
sleep(Math.random() * 0.5);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function handleSummary(data) {
|
|
149
|
+
const { metrics } = data;
|
|
150
|
+
|
|
151
|
+
// Calculate stress test specific metrics
|
|
152
|
+
const totalRequests = metrics.http_reqs?.values?.count || 0;
|
|
153
|
+
const failedRequests = metrics.http_req_failed?.values?.passes || 0;
|
|
154
|
+
const errorPercentage =
|
|
155
|
+
totalRequests > 0 ? ((failedRequests / totalRequests) * 100).toFixed(2) : 0;
|
|
156
|
+
|
|
157
|
+
const summary = {
|
|
158
|
+
timestamp: new Date().toISOString(),
|
|
159
|
+
test_type: "stress",
|
|
160
|
+
total_requests: totalRequests,
|
|
161
|
+
failed_requests: failedRequests,
|
|
162
|
+
error_percentage: parseFloat(errorPercentage),
|
|
163
|
+
avg_response_time: Math.round(metrics.http_req_duration?.values?.avg || 0),
|
|
164
|
+
p95_response_time: Math.round(
|
|
165
|
+
metrics.http_req_duration?.values["p(95)"] || 0
|
|
166
|
+
),
|
|
167
|
+
p99_response_time: Math.round(
|
|
168
|
+
metrics.http_req_duration?.values["p(99)"] || 0
|
|
169
|
+
),
|
|
170
|
+
max_response_time: Math.round(metrics.http_req_duration?.values?.max || 0),
|
|
171
|
+
custom_metrics: {
|
|
172
|
+
error_rate: metrics.errors?.values?.rate || 0,
|
|
173
|
+
throughput_rate: metrics.throughput?.values?.rate || 0,
|
|
174
|
+
avg_custom_response_time: Math.round(
|
|
175
|
+
metrics.custom_response_time?.values?.avg || 0
|
|
176
|
+
),
|
|
177
|
+
},
|
|
178
|
+
thresholds_passed: Object.entries(data.thresholds || {}).every(
|
|
179
|
+
([, value]) => value.ok
|
|
180
|
+
),
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
"k6-results/stress-summary.json": JSON.stringify(summary, null, 2),
|
|
185
|
+
stdout: createTextSummary(summary),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function createTextSummary(summary) {
|
|
190
|
+
let text = "\n=== Stress Test Summary ===\n\n";
|
|
191
|
+
text += `Test Type: ${summary.test_type}\n`;
|
|
192
|
+
text += `Timestamp: ${summary.timestamp}\n\n`;
|
|
193
|
+
text += `Total Requests: ${summary.total_requests}\n`;
|
|
194
|
+
text += `Failed Requests: ${summary.failed_requests} (${summary.error_percentage}%)\n`;
|
|
195
|
+
text += `Average Response Time: ${summary.avg_response_time}ms\n`;
|
|
196
|
+
text += `95th Percentile: ${summary.p95_response_time}ms\n`;
|
|
197
|
+
text += `99th Percentile: ${summary.p99_response_time}ms\n`;
|
|
198
|
+
text += `Max Response Time: ${summary.max_response_time}ms\n\n`;
|
|
199
|
+
text += `Custom Metrics:\n`;
|
|
200
|
+
text += ` Error Rate: ${(summary.custom_metrics.error_rate * 100).toFixed(2)}%\n`;
|
|
201
|
+
text += ` Throughput Rate: ${(summary.custom_metrics.throughput_rate * 100).toFixed(2)}%\n`;
|
|
202
|
+
text += ` Avg Custom Response Time: ${summary.custom_metrics.avg_custom_response_time}ms\n\n`;
|
|
203
|
+
text += `All Thresholds Passed: ${summary.thresholds_passed ? "✅ YES" : "❌ NO"}\n`;
|
|
204
|
+
|
|
205
|
+
return text;
|
|
206
|
+
}
|